From 41e8e9828368cfae0469414b241de23b2f2a2808 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 23 Aug 2017 14:16:06 +0700
Subject: [PATCH 001/118] Setup env for XenForo 2
---
dev-scripts/Dockerfile.php-apache | 2 +-
dev-scripts/docker/cmd.sh | 2 +-
dev-scripts/docker/find-addons.sh | 10 -
dev-scripts/docker/find-addons2.sh | 10 +
router.php | 4 +-
src/addons/DevHelper/Autoloader.php | 77 +++++
src/addons/DevHelper/DevHelper/Hint/XF.php | 9 +
src/addons/DevHelper/Router.php | 161 ++++++++++
src/addons/DevHelper/StreamWrapper.php | 327 +++++++++++++++++++++
src/addons/DevHelper/XF/AddOn/Manager.php | 73 +++++
src/addons/DevHelper/XF/Extension.php | 28 ++
src/addons/DevHelper/XF/XF.php | 11 +
src/addons/DevHelper/addon.json | 6 +
13 files changed, 706 insertions(+), 14 deletions(-)
delete mode 100644 dev-scripts/docker/find-addons.sh
create mode 100644 dev-scripts/docker/find-addons2.sh
create mode 100644 src/addons/DevHelper/Autoloader.php
create mode 100644 src/addons/DevHelper/DevHelper/Hint/XF.php
create mode 100644 src/addons/DevHelper/Router.php
create mode 100644 src/addons/DevHelper/StreamWrapper.php
create mode 100644 src/addons/DevHelper/XF/AddOn/Manager.php
create mode 100644 src/addons/DevHelper/XF/Extension.php
create mode 100644 src/addons/DevHelper/XF/XF.php
create mode 100644 src/addons/DevHelper/addon.json
diff --git a/dev-scripts/Dockerfile.php-apache b/dev-scripts/Dockerfile.php-apache
index f43b292..b7fb859 100644
--- a/dev-scripts/Dockerfile.php-apache
+++ b/dev-scripts/Dockerfile.php-apache
@@ -12,6 +12,6 @@ RUN pecl install apcu \
COPY docker/* /usr/local/bin/
RUN chmod +x /usr/local/bin/cmd.sh \
- && chmod +x /usr/local/bin/find-addons.sh
+ && chmod +x /usr/local/bin/find-addons2.sh
CMD ["/usr/local/bin/cmd.sh"]
diff --git a/dev-scripts/docker/cmd.sh b/dev-scripts/docker/cmd.sh
index 3633003..8a5f914 100644
--- a/dev-scripts/docker/cmd.sh
+++ b/dev-scripts/docker/cmd.sh
@@ -2,6 +2,6 @@
set -e
-find-addons.sh
+find-addons2.sh
exec apache2-foreground
diff --git a/dev-scripts/docker/find-addons.sh b/dev-scripts/docker/find-addons.sh
deleted file mode 100644
index bd5c8f4..0000000
--- a/dev-scripts/docker/find-addons.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-set -e
-
-cd /var/www/html/
-
-{ \
- echo "# Generated by $0"; \
- find addons/ -maxdepth 5 -name library -type d | grep -v .git | sed 's#/library$##'; \
-} > ./xenforo/internal_data/addons.txt
diff --git a/dev-scripts/docker/find-addons2.sh b/dev-scripts/docker/find-addons2.sh
new file mode 100644
index 0000000..70c7b30
--- /dev/null
+++ b/dev-scripts/docker/find-addons2.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -e
+
+cd /var/www/html/
+
+{ \
+ echo "# Generated by $0"; \
+ find addons/ -maxdepth 5 -name src -type d | grep -v .git | sed 's#/src##'; \
+} > ./xenforo/internal_data/addons2.txt
diff --git a/router.php b/router.php
index 1c857b1..563c29a 100644
--- a/router.php
+++ b/router.php
@@ -1,4 +1,4 @@
_setup) {
+ return;
+ }
+
+ /** @var \Composer\Autoload\ClassLoader $autoLoader */
+ /** @noinspection PhpIncludeInspection */
+ $autoLoader = require('/var/www/html/xenforo/src/vendor/autoload.php');
+
+ list(, $addOnPaths) = Router::getLocatePaths();
+ foreach ($addOnPaths as $addOnPath) {
+ $globPattern = $addOnPath . '/src/addons/*';
+ foreach (glob($globPattern) as $rootPath) {
+ $rootNamespace = basename($rootPath);
+ $autoLoader->setPsr4($rootNamespace . '\\', $rootPath);
+ }
+ }
+
+ spl_autoload_register(array($this, 'autoload'), true, true);
+
+ $this->_setup = true;
+ }
+
+ public function autoload($class)
+ {
+ if (class_exists($class, false) || interface_exists($class, false)) {
+ return true;
+ }
+
+ if ($class == 'utf8_entity_decoder') {
+ return false;
+ }
+
+ if (substr($class, 0, 5) == 'XFCP_') {
+ return false;
+ }
+
+ file_put_contents('/var/www/html/xenforo/internal_data/autoload.log', $class . "\n", FILE_APPEND);
+ switch ($class) {
+ case 'XF\Extension':
+ $original = file_get_contents('/var/www/html/xenforo/src/XF/Extension.php');
+ $renamed = str_replace('class Extension', 'class _Extension', $original);
+ eval(substr($renamed, strlen('setupAutoloader();
+ }
+
+ public static function locateCached($fullPath)
+ {
+ $key = basename($fullPath) . md5($fullPath);
+ $cached = apcu_fetch($key);
+ $located = null;
+ $success = false;
+ if (is_array($cached)
+ && $cached['fullPath'] === $fullPath) {
+ $located = $cached['located'];
+ }
+
+ if (empty($located)) {
+ $located = static::locate($fullPath, $success);
+ }
+
+ if ($success) {
+ $cacheEntry = array(
+ 'fullPath' => $fullPath,
+ 'located' => $located,
+ );
+
+ apcu_store($key, $cacheEntry);
+ }
+
+ return $located;
+ }
+
+ public static function locate($fullPath, &$success = null)
+ {
+ if (file_exists($fullPath)) {
+ $success = true;
+ return $fullPath;
+ }
+
+ list($xenforoDir, $addOnPaths) = static::getLocatePaths();
+ $shortened = preg_replace(
+ '#^' . preg_quote($xenforoDir, '#') . '#',
+ '',
+ $fullPath
+ );
+
+ foreach ($addOnPaths as $addOnPath) {
+ $candidatePath = $addOnPath . $shortened;
+ if (file_exists($candidatePath)) {
+ $success = true;
+ return $candidatePath;
+ }
+ }
+
+ return $fullPath;
+ }
+
+ public static function getLocatePaths()
+ {
+ static $xenforoDir = null;
+ static $addOnPaths = null;
+
+ if ($addOnPaths === null) {
+ $routerPhp = $_SERVER['DEVHELPER_ROUTER_PHP'];
+ $routerPhpDir = dirname($routerPhp);
+ $xenforoDir = sprintf('%s/xenforo', $routerPhpDir);
+ $addOnPaths = array();
+
+ $txtPath = sprintf('%s/internal_data/addons2.txt', $xenforoDir);
+ $lines = array();
+ if (file_exists($txtPath)) {
+ $lines = file($txtPath);
+ }
+ $prefix = 'addons';
+ $prefixLength = strlen($prefix);
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if (strlen($line) === 0) {
+ continue;
+ }
+
+ if (substr($line, 0, $prefixLength) !== $prefix) {
+ continue;
+ }
+
+ $addOnPaths[] = sprintf('%s/%s', $routerPhpDir, $line);
+ }
+ }
+
+ return array($xenforoDir, $addOnPaths);
+ }
+}
diff --git a/src/addons/DevHelper/StreamWrapper.php b/src/addons/DevHelper/StreamWrapper.php
new file mode 100644
index 0000000..477f45e
--- /dev/null
+++ b/src/addons/DevHelper/StreamWrapper.php
@@ -0,0 +1,327 @@
+_dirHandle)) {
+ return false;
+ }
+
+ closedir($this->_dirHandle);
+
+ $this->_dirPath = null;
+ $this->_dirHandle = null;
+
+ return true;
+ }
+
+ /**
+ * Open directory handle
+ *
+ * @param string $path
+ * @param int $options
+ * @return bool
+ */
+ public function dir_opendir($path, $options)
+ {
+ $located = $this->locate($path);
+ if (!file_exists($located)) {
+ return false;
+ }
+
+ $this->_dirPath = $path;
+ $this->_dirHandle = opendir($located);
+
+ return true;
+ }
+
+ /**
+ * Read entry from directory handle
+ *
+ * @return string|false
+ */
+ public function dir_readdir()
+ {
+ if (empty($this->_dirHandle)) {
+ return false;
+ }
+
+ return readdir($this->_dirHandle);
+ }
+
+ /**
+ * Rewind directory handle
+ *
+ * @return bool
+ */
+ public function dir_rewinddir()
+ {
+ if (empty($this->_dirHandle)) {
+ return false;
+ }
+
+ return rewinddir($this->_dirHandle);
+ }
+
+ /**
+ * Create a directory
+ *
+ * @param string $path
+ * @param int $mode
+ * @param int $options
+ * @return bool
+ */
+ public function mkdir($path, $mode, $options)
+ {
+ $located = $this->locate($path);
+ if (!file_exists($located)) {
+ $locatedDir = $this->locate(dirname($path));
+ if (is_dir($locatedDir)) {
+ $located = $locatedDir . DIRECTORY_SEPARATOR . basename($path);
+ }
+ }
+
+ return mkdir($located, $mode, $options);
+ }
+
+ /**
+ * Retrieve the under laying resource
+ *
+ * @param int $castAs
+ * @return resource
+ */
+ public function stream_cast($castAs)
+ {
+ if (empty($this->_streamHandle)) {
+ return null;
+ }
+
+ return $this->_streamHandle;
+ }
+
+ /**
+ * Close an resource
+ */
+ public function stream_close()
+ {
+ if (empty($this->_streamHandle)) {
+ return;
+ }
+
+ fclose($this->_streamHandle);
+
+ $this->_streamPath = null;
+ $this->_streamHandle = null;
+ }
+
+ /**
+ * Tests for end-of-file on a file pointer
+ *
+ * @return bool
+ */
+ public function stream_eof()
+ {
+ if (empty($this->_streamHandle)) {
+ return true;
+ }
+
+ return feof($this->_streamHandle);
+ }
+
+ /**
+ * Advisory file locking
+ *
+ * @param $operation
+ * @return bool
+ */
+ public function stream_lock($operation)
+ {
+ if (empty($this->_streamHandle)) {
+ return false;
+ }
+
+ return flock($this->_streamHandle, $operation);
+ }
+
+ /**
+ * Change stream metadata
+ *
+ * @param string $path
+ * @param int $option
+ * @param mixed $value
+ * @return bool
+ */
+ public function stream_metadata($path, $option, $value)
+ {
+ switch ($option) {
+ case STREAM_META_ACCESS:
+ $located = $this->locate($path);
+ return chmod($located, $value);
+ }
+
+ return false;
+ }
+
+ /**
+ * Opens file or URL
+ *
+ * @param string $path
+ * @param string $mode
+ * @param int $options
+ * @param string &$openedPath
+ * @return bool
+ */
+ public function stream_open($path, $mode, $options, &$openedPath)
+ {
+ $needCreate = false;
+ if (strpos($mode, 'c') !== false) {
+ // modes: c, c+
+ $needCreate = true;
+ } elseif (strpos($mode, 'a') !== false
+ || strpos($mode, '+') !== false
+ ) {
+ // modes: r+, w+, a, a+, x+
+ $needCreate = true;
+ } elseif (strpos($mode, 'w') !== false
+ || strpos($mode, 'x') !== false
+ ) {
+ // modes: w, x
+ $needCreate = true;
+ }
+
+ $located = $this->locate($path);
+ if ($needCreate && !file_exists($located)) {
+ $locatedDir = $this->locate(dirname($path));
+ if (is_dir($locatedDir)) {
+ $located = $locatedDir . DIRECTORY_SEPARATOR . basename($path);
+ }
+ }
+
+ $this->_streamPath = $path;
+ $this->_streamHandle = fopen($located, $mode);
+
+ return true;
+ }
+
+ /**
+ * Read from stream
+ *
+ * @param int $count
+ * @return string
+ */
+ public function stream_read($count)
+ {
+ if (empty($this->_streamHandle)) {
+ return '';
+ }
+
+ return fread($this->_streamHandle, $count);
+ }
+
+ public function stream_seek($offset, $whence = SEEK_SET)
+ {
+ if (empty($this->_streamHandle)) {
+ return false;
+ }
+
+ return fseek($this->_streamHandle, $offset, $whence);
+ }
+
+ /**
+ * Retrieve information about a file resource
+ *
+ * @return array|false
+ */
+ public function stream_stat()
+ {
+ if (empty($this->_streamHandle)) {
+ return false;
+ }
+
+ return fstat($this->_streamHandle);
+ }
+
+ /**
+ * Write to stream
+ *
+ * @param string $data
+ * @return int
+ */
+ public function stream_write($data)
+ {
+ if (empty($this->_streamHandle)) {
+ return 0;
+ }
+
+ $written = fwrite($this->_streamHandle, $data);
+
+ return $written;
+ }
+
+ /**
+ * Delete a file
+ *
+ * @param string $path
+ * @return bool
+ */
+ public function unlink($path)
+ {
+ $located = $this->locate($path);
+ if (!file_exists($located)) {
+ return false;
+ }
+
+ return unlink($located);
+ }
+
+ /**
+ * Retrieve information about a file
+ *
+ * @param string $path
+ * @param int $flags
+ * @return array|false
+ */
+ public function url_stat($path, $flags)
+ {
+ $located = $this->locate($path);
+ if (!file_exists($located)) {
+ return false;
+ }
+
+ return stat($located);
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ * @throws \Exception
+ */
+ private function locate($path)
+ {
+ $protocolLength = strlen(static::PROTOCOL);
+ if (substr($path, 0, $protocolLength) !== static::PROTOCOL) {
+ throw new \Exception('Unrecognized path ' . $path);
+ }
+
+ $pathWithoutProtocol = substr($path, $protocolLength + 3);
+ list($xenforoDir,) = Router::getLocatePaths();
+ $pathFromRoot = $xenforoDir . DIRECTORY_SEPARATOR . $pathWithoutProtocol;
+
+ return Router::locateCached($pathFromRoot);
+ }
+}
\ No newline at end of file
diff --git a/src/addons/DevHelper/XF/AddOn/Manager.php b/src/addons/DevHelper/XF/AddOn/Manager.php
new file mode 100644
index 0000000..27892fd
--- /dev/null
+++ b/src/addons/DevHelper/XF/AddOn/Manager.php
@@ -0,0 +1,73 @@
+jsonInfo)) {
+ $skip = false;
+ }
+
+ $available = parent::getAllJsonInfo();
+
+ if (!$skip) {
+ $addOnIds = $this->DevHelper_getAvailableAddOnIds();
+ foreach ($addOnIds as $addOnId) {
+ $available[$addOnId] = $this->getAddOnJsonInfo($addOnId);
+ }
+
+ $this->jsonInfo = $available;
+ }
+
+ return $available;
+ }
+
+ public function getAddOnPath($addOnId)
+ {
+ $addOnPath = parent::getAddOnPath($addOnId);
+ return Router::locateCached($addOnPath);
+ }
+
+ private function DevHelper_getAvailableAddOnIds()
+ {
+ $addOnIds = [];
+
+ list(, $addOnPaths) = Router::getLocatePaths();
+
+ foreach ($addOnPaths as $addOnPath) {
+ foreach (new \DirectoryIterator($addOnPath . '/src/addons') as $entry) {
+ if (!$this->isValidDir($entry)) {
+ continue;
+ }
+
+ if ($this->isDirAddOnRoot($entry)) {
+ $addOnIds[] = $entry->getBasename();
+ } else {
+ $vendorPrefix = $entry->getBasename();
+ foreach (new \DirectoryIterator($entry->getPathname()) as $addOnDir) {
+ if (!$this->isValidDir($addOnDir)) {
+ continue;
+ }
+
+ if ($this->isDirAddOnRoot($addOnDir)) {
+ $addOnIds[] = "$vendorPrefix/{$addOnDir->getBasename()}";
+ }
+ }
+ }
+ }
+ }
+
+ return $addOnIds;
+ }
+}
+
+if (false) {
+ class XFCP_Manager extends \XF\AddOn\Manager
+ {
+ }
+}
diff --git a/src/addons/DevHelper/XF/Extension.php b/src/addons/DevHelper/XF/Extension.php
new file mode 100644
index 0000000..5965bcb
--- /dev/null
+++ b/src/addons/DevHelper/XF/Extension.php
@@ -0,0 +1,28 @@
+addClassExtension($class, 'DevHelper\\' . $class);
+ }
+ }
+
+ public function extendClass($class, $fakeBaseClass = null)
+ {
+ return parent::extendClass($class, $fakeBaseClass);
+ }
+}
diff --git a/src/addons/DevHelper/XF/XF.php b/src/addons/DevHelper/XF/XF.php
new file mode 100644
index 0000000..76c30eb
--- /dev/null
+++ b/src/addons/DevHelper/XF/XF.php
@@ -0,0 +1,11 @@
+
Date: Wed, 23 Aug 2017 14:24:35 +0700
Subject: [PATCH 002/118] Add support for cmd.php
---
cmd.php | 4 ++++
src/addons/DevHelper/.gitignore | 1 +
src/addons/DevHelper/Router.php | 18 +++++++++++-------
src/addons/DevHelper/addon.json | 17 +++++++++++++----
4 files changed, 29 insertions(+), 11 deletions(-)
create mode 100644 cmd.php
create mode 100644 src/addons/DevHelper/.gitignore
diff --git a/cmd.php b/cmd.php
new file mode 100644
index 0000000..7478b65
--- /dev/null
+++ b/cmd.php
@@ -0,0 +1,4 @@
+
Date: Wed, 23 Aug 2017 15:24:36 +0700
Subject: [PATCH 003/118] Add support for xf-addon:build-release
---
src/addons/DevHelper/.gitignore | 2 +
src/addons/DevHelper/StreamWrapper.php | 37 +++++---
src/addons/DevHelper/XF/Extension.php | 3 +-
.../XF/Service/AddOn/ReleaseBuilder.php | 89 +++++++++++++++++++
4 files changed, 120 insertions(+), 11 deletions(-)
create mode 100644 src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
diff --git a/src/addons/DevHelper/.gitignore b/src/addons/DevHelper/.gitignore
index ba65b13..038757a 100644
--- a/src/addons/DevHelper/.gitignore
+++ b/src/addons/DevHelper/.gitignore
@@ -1 +1,3 @@
/_build/
+/_output/
+/_releases/
diff --git a/src/addons/DevHelper/StreamWrapper.php b/src/addons/DevHelper/StreamWrapper.php
index 477f45e..5e94f12 100644
--- a/src/addons/DevHelper/StreamWrapper.php
+++ b/src/addons/DevHelper/StreamWrapper.php
@@ -39,7 +39,7 @@ public function dir_closedir()
*/
public function dir_opendir($path, $options)
{
- $located = $this->locate($path);
+ $located = static::locate($path);
if (!file_exists($located)) {
return false;
}
@@ -88,9 +88,9 @@ public function dir_rewinddir()
*/
public function mkdir($path, $mode, $options)
{
- $located = $this->locate($path);
+ $located = static::locate($path);
if (!file_exists($located)) {
- $locatedDir = $this->locate(dirname($path));
+ $locatedDir = static::locate(dirname($path));
if (is_dir($locatedDir)) {
$located = $locatedDir . DIRECTORY_SEPARATOR . basename($path);
}
@@ -99,6 +99,21 @@ public function mkdir($path, $mode, $options)
return mkdir($located, $mode, $options);
}
+ /**
+ * @param string $path
+ * @param int $options
+ * @return bool
+ */
+ public function rmdir($path, $options)
+ {
+ $located = static::locate($path);
+ if (!file_exists($located)) {
+ return false;
+ }
+
+ return rmdir($path);
+ }
+
/**
* Retrieve the under laying resource
*
@@ -170,7 +185,7 @@ public function stream_metadata($path, $option, $value)
{
switch ($option) {
case STREAM_META_ACCESS:
- $located = $this->locate($path);
+ $located = static::locate($path);
return chmod($located, $value);
}
@@ -204,9 +219,9 @@ public function stream_open($path, $mode, $options, &$openedPath)
$needCreate = true;
}
- $located = $this->locate($path);
+ $located = static::locate($path);
if ($needCreate && !file_exists($located)) {
- $locatedDir = $this->locate(dirname($path));
+ $locatedDir = static::locate(dirname($path));
if (is_dir($locatedDir)) {
$located = $locatedDir . DIRECTORY_SEPARATOR . basename($path);
}
@@ -281,7 +296,7 @@ public function stream_write($data)
*/
public function unlink($path)
{
- $located = $this->locate($path);
+ $located = static::locate($path);
if (!file_exists($located)) {
return false;
}
@@ -298,12 +313,14 @@ public function unlink($path)
*/
public function url_stat($path, $flags)
{
- $located = $this->locate($path);
+ $located = static::locate($path);
if (!file_exists($located)) {
return false;
}
- return stat($located);
+ $stat = stat($located);
+
+ return $stat;
}
/**
@@ -311,7 +328,7 @@ public function url_stat($path, $flags)
* @return string
* @throws \Exception
*/
- private function locate($path)
+ public static function locate($path)
{
$protocolLength = strlen(static::PROTOCOL);
if (substr($path, 0, $protocolLength) !== static::PROTOCOL) {
diff --git a/src/addons/DevHelper/XF/Extension.php b/src/addons/DevHelper/XF/Extension.php
index 5965bcb..cefd4d3 100644
--- a/src/addons/DevHelper/XF/Extension.php
+++ b/src/addons/DevHelper/XF/Extension.php
@@ -14,7 +14,8 @@ public function __construct(array $listeners = [], array $classExtensions = [])
XF::setSourceDirectory(StreamWrapper::PROTOCOL . '://src');
$classes = [
- 'XF\AddOn\Manager'
+ 'XF\AddOn\Manager',
+ 'XF\Service\AddOn\ReleaseBuilder',
];
foreach ($classes as $class) {
$this->addClassExtension($class, 'DevHelper\\' . $class);
diff --git a/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
new file mode 100644
index 0000000..6c0dfcc
--- /dev/null
+++ b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
@@ -0,0 +1,89 @@
+buildRoot = '/var/www/html/xenforo/internal_data/builds/' . $this->addOn->getAddOnId();
+ }
+
+ protected function prepareFilesToCopy()
+ {
+ parent::prepareFilesToCopy();
+
+ $srcUptoNamespace = $this->addOnRoot;
+ $srcUptoNamespaceLocated = StreamWrapper::locate($srcUptoNamespace);
+ $repoPath = preg_replace('#/src/addons.+$#', '', $srcUptoNamespaceLocated);
+ $ds = DIRECTORY_SEPARATOR;
+
+ $dirPaths = [];
+ foreach (new \DirectoryIterator($repoPath) as $dirCandidate) {
+ if (!$dirCandidate->isDir()
+ || $dirCandidate->isDot()) {
+ continue;
+ }
+
+ $skip = false;
+ $basename = $dirCandidate->getBasename();
+ switch ($basename) {
+ case '.git':
+ case 'addons':
+ case 'dev-scripts':
+ case 'library':
+ case 'src':
+ case 'xenforo':
+ $skip = true;
+ break;
+ }
+ if ($skip) {
+ continue;
+ }
+
+ $dirPaths[] = $dirCandidate->getPathname();
+ }
+
+ foreach ($dirPaths as $dirPath) {
+ $dirStandardized = $this->standardizePath($repoPath, $dirPath);
+
+ $filesIterator = $this->getFileIterator($dirPath);
+ foreach ($filesIterator AS $file) {
+ if ($file->isDir()) {
+ continue;
+ }
+
+ $fileStandardized = $this->standardizePath($dirPath, $file->getPathname());
+ $destination = $this->uploadRoot . $ds . $dirStandardized . $ds . $fileStandardized;
+ File::copyFile($file->getPathname(), $destination, false);
+ }
+ }
+ }
+
+ public function finalizeRelease()
+ {
+ $addOnId = $this->addOn->prepareAddOnIdForFilename();
+ $versionString = $this->addOn->prepareVersionForFilename();
+ $releasePath = sprintf(
+ '/var/www/html/xenforo/internal_data/releases/%1$s/%1$s-%2$s.zip',
+ $addOnId,
+ $versionString
+ );
+
+ File::createDirectory(dirname($releasePath), false);
+ File::renameFile($this->tempFile, $releasePath, false);
+
+ // intentionally do not call parent
+ }
+}
+
+if (false) {
+ class XFCP_ReleaseBuilder extends \XF\Service\AddOn\ReleaseBuilder
+ {
+ }
+}
\ No newline at end of file
From 3a93ab32fa095bcca9812293e14aa68eeac72bd8 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 23 Aug 2017 15:55:13 +0700
Subject: [PATCH 004/118] Fix bug rmdir
---
src/addons/DevHelper/StreamWrapper.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/addons/DevHelper/StreamWrapper.php b/src/addons/DevHelper/StreamWrapper.php
index 5e94f12..20cb91e 100644
--- a/src/addons/DevHelper/StreamWrapper.php
+++ b/src/addons/DevHelper/StreamWrapper.php
@@ -111,7 +111,7 @@ public function rmdir($path, $options)
return false;
}
- return rmdir($path);
+ return rmdir($located);
}
/**
From 9d2cfd5889872437344cb5903c85a65fa3b0ae82 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 23 Aug 2017 15:55:46 +0700
Subject: [PATCH 005/118] Improve support for xf-addon:build-release
---
src/addons/DevHelper/XF/AddOn/AddOn.php | 24 +++++++++++++++++++
src/addons/DevHelper/XF/AddOn/Manager.php | 9 +++++++
src/addons/DevHelper/XF/Extension.php | 6 +----
.../XF/Service/AddOn/ReleaseBuilder.php | 22 ++++-------------
4 files changed, 38 insertions(+), 23 deletions(-)
create mode 100644 src/addons/DevHelper/XF/AddOn/AddOn.php
diff --git a/src/addons/DevHelper/XF/AddOn/AddOn.php b/src/addons/DevHelper/XF/AddOn/AddOn.php
new file mode 100644
index 0000000..97af710
--- /dev/null
+++ b/src/addons/DevHelper/XF/AddOn/AddOn.php
@@ -0,0 +1,24 @@
+prepareAddOnIdForFilename();
+ $versionString = $this->prepareVersionForFilename();
+ /** @noinspection PhpUndefinedFieldInspection */
+ $this->buildDir = '/var/www/html/xenforo/internal_data/builds/' . $addOnId . '/' . $versionString;
+ /** @noinspection PhpUndefinedFieldInspection */
+ $this->releasesDir = '/var/www/html/xenforo/internal_data/releases/' . $addOnId;
+ }
+}
+
+if (false) {
+ class XFCP_AddOn extends \XF\AddOn\AddOn
+ {
+ }
+}
diff --git a/src/addons/DevHelper/XF/AddOn/Manager.php b/src/addons/DevHelper/XF/AddOn/Manager.php
index 27892fd..3d81661 100644
--- a/src/addons/DevHelper/XF/AddOn/Manager.php
+++ b/src/addons/DevHelper/XF/AddOn/Manager.php
@@ -33,6 +33,15 @@ public function getAddOnPath($addOnId)
return Router::locateCached($addOnPath);
}
+ protected function loadAddOnClass($addOnOrId, array $jsonInfo = null)
+ {
+ $addOn = parent::loadAddOnClass($addOnOrId, $jsonInfo);
+
+ $class = \XF::app()->extendClass('XF\AddOn\AddOn');
+
+ return new $class($addOnOrId ?: $addOn->getAddOnId());
+ }
+
private function DevHelper_getAvailableAddOnIds()
{
$addOnIds = [];
diff --git a/src/addons/DevHelper/XF/Extension.php b/src/addons/DevHelper/XF/Extension.php
index cefd4d3..54d9925 100644
--- a/src/addons/DevHelper/XF/Extension.php
+++ b/src/addons/DevHelper/XF/Extension.php
@@ -14,6 +14,7 @@ public function __construct(array $listeners = [], array $classExtensions = [])
XF::setSourceDirectory(StreamWrapper::PROTOCOL . '://src');
$classes = [
+ 'XF\AddOn\AddOn',
'XF\AddOn\Manager',
'XF\Service\AddOn\ReleaseBuilder',
];
@@ -21,9 +22,4 @@ public function __construct(array $listeners = [], array $classExtensions = [])
$this->addClassExtension($class, 'DevHelper\\' . $class);
}
}
-
- public function extendClass($class, $fakeBaseClass = null)
- {
- return parent::extendClass($class, $fakeBaseClass);
- }
}
diff --git a/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
index 6c0dfcc..ae1e25d 100644
--- a/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
+++ b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
@@ -7,13 +7,6 @@
class ReleaseBuilder extends XFCP_ReleaseBuilder
{
- protected function prepareDirectories()
- {
- parent::prepareDirectories();
-
- $this->buildRoot = '/var/www/html/xenforo/internal_data/builds/' . $this->addOn->getAddOnId();
- }
-
protected function prepareFilesToCopy()
{
parent::prepareFilesToCopy();
@@ -67,18 +60,11 @@ protected function prepareFilesToCopy()
public function finalizeRelease()
{
- $addOnId = $this->addOn->prepareAddOnIdForFilename();
- $versionString = $this->addOn->prepareVersionForFilename();
- $releasePath = sprintf(
- '/var/www/html/xenforo/internal_data/releases/%1$s/%1$s-%2$s.zip',
- $addOnId,
- $versionString
- );
-
- File::createDirectory(dirname($releasePath), false);
- File::renameFile($this->tempFile, $releasePath, false);
+ $oopsPath = $this->buildRoot . '/oops';
+ File::createDirectory($oopsPath, false);
+ $this->buildRoot = $oopsPath;
- // intentionally do not call parent
+ parent::finalizeRelease();
}
}
From 029fa70e5ab19388bab30f82f3c45f7c4eeb0b87 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 30 Aug 2017 13:26:56 +0700
Subject: [PATCH 006/118] Improve support for locate
---
dev-scripts/docker/find-addons2.sh | 2 +-
src/addons/DevHelper/Router.php | 32 +++++++++++---
src/addons/DevHelper/StreamWrapper.php | 36 +++++++++++++++-
src/addons/DevHelper/XF/AddOn/Manager.php | 51 +----------------------
4 files changed, 63 insertions(+), 58 deletions(-)
diff --git a/dev-scripts/docker/find-addons2.sh b/dev-scripts/docker/find-addons2.sh
index 70c7b30..b28adb9 100644
--- a/dev-scripts/docker/find-addons2.sh
+++ b/dev-scripts/docker/find-addons2.sh
@@ -6,5 +6,5 @@ cd /var/www/html/
{ \
echo "# Generated by $0"; \
- find addons/ -maxdepth 5 -name src -type d | grep -v .git | sed 's#/src##'; \
+ find addons/ -maxdepth 5 -name .git -type d | sed 's#/.git##'; \
} > ./xenforo/internal_data/addons2.txt
diff --git a/src/addons/DevHelper/Router.php b/src/addons/DevHelper/Router.php
index c197250..6a20901 100644
--- a/src/addons/DevHelper/Router.php
+++ b/src/addons/DevHelper/Router.php
@@ -116,11 +116,23 @@ public static function locate($fullPath, &$success = null)
$fullPath
);
- foreach ($addOnPaths as $addOnPath) {
- $candidatePath = $addOnPath . $shortened;
- if (file_exists($candidatePath)) {
- $success = true;
- return $candidatePath;
+ foreach ($addOnPaths as $addOnPathSuffix => $addOnPath) {
+ $candidatePaths = [];
+
+ if (strpos($shortened, '/src/addons/') === 0) {
+ $relativeRegex = '#^.+?' . preg_quote($addOnPathSuffix, '#') . '#';
+ $relativePath = preg_replace($relativeRegex, '', $shortened, -1, $count);
+ if ($count === 1) {
+ $candidatePaths[] = $addOnPath . $shortened;
+ $candidatePaths[] = $addOnPath . $relativePath;
+ }
+ }
+
+ foreach ($candidatePaths as $candidatePath) {
+ if (file_exists($candidatePath)) {
+ $success = true;
+ return $candidatePath;
+ }
}
}
@@ -156,10 +168,18 @@ public static function getLocatePaths()
continue;
}
- $addOnPaths[] = sprintf('%s/%s', $routerPhpDir, $line);
+ $addOnPath = sprintf('%s/%s', $routerPhpDir, $line);
+ $addOnPathSuffix = trim(preg_replace('#^.+addons#', '', $addOnPath), '/');
+ $addOnPaths[$addOnPathSuffix] = $addOnPath;
}
}
return array($xenforoDir, $addOnPaths);
}
+
+ public static function locateReset()
+ {
+ apcu_clear_cache();
+ exec('/usr/local/bin/find-addons2.sh');
+ }
}
diff --git a/src/addons/DevHelper/StreamWrapper.php b/src/addons/DevHelper/StreamWrapper.php
index 20cb91e..76fe106 100644
--- a/src/addons/DevHelper/StreamWrapper.php
+++ b/src/addons/DevHelper/StreamWrapper.php
@@ -8,6 +8,7 @@ class StreamWrapper
private $_dirPath;
private $_dirHandle;
+ private $_dirSecondHandle;
private $_streamPath;
private $_streamHandle;
@@ -24,8 +25,13 @@ public function dir_closedir()
closedir($this->_dirHandle);
+ if (!empty($this->_dirSecondHandle)) {
+ closedir($this->_dirSecondHandle);
+ }
+
$this->_dirPath = null;
$this->_dirHandle = null;
+ $this->_dirSecondHandle = null;
return true;
}
@@ -47,6 +53,10 @@ public function dir_opendir($path, $options)
$this->_dirPath = $path;
$this->_dirHandle = opendir($located);
+ if ($located === '/var/www/html/xenforo/src/addons') {
+ $this->_dirSecondHandle = opendir('/var/www/html/addons');
+ }
+
return true;
}
@@ -61,7 +71,22 @@ public function dir_readdir()
return false;
}
- return readdir($this->_dirHandle);
+ $read = readdir($this->_dirHandle);
+
+ if ($read === false && !empty($this->_dirSecondHandle)) {
+ while (true) {
+ $secondRead = readdir($this->_dirSecondHandle);
+ switch ($secondRead) {
+ case '.':
+ case '..':
+ continue;
+ }
+
+ return $secondRead;
+ }
+ }
+
+ return $read;
}
/**
@@ -75,7 +100,13 @@ public function dir_rewinddir()
return false;
}
- return rewinddir($this->_dirHandle);
+ $bool = rewinddir($this->_dirHandle);
+
+ if (!empty($this->_dirSecondHandle)) {
+ rewind($this->_dirSecondHandle);
+ }
+
+ return $bool;
}
/**
@@ -314,6 +345,7 @@ public function unlink($path)
public function url_stat($path, $flags)
{
$located = static::locate($path);
+
if (!file_exists($located)) {
return false;
}
diff --git a/src/addons/DevHelper/XF/AddOn/Manager.php b/src/addons/DevHelper/XF/AddOn/Manager.php
index 3d81661..3cf7191 100644
--- a/src/addons/DevHelper/XF/AddOn/Manager.php
+++ b/src/addons/DevHelper/XF/AddOn/Manager.php
@@ -8,23 +8,8 @@ class Manager extends XFCP_Manager
{
protected function getAllJsonInfo()
{
- $skip = true;
- if (!is_array($this->jsonInfo)) {
- $skip = false;
- }
-
- $available = parent::getAllJsonInfo();
-
- if (!$skip) {
- $addOnIds = $this->DevHelper_getAvailableAddOnIds();
- foreach ($addOnIds as $addOnId) {
- $available[$addOnId] = $this->getAddOnJsonInfo($addOnId);
- }
-
- $this->jsonInfo = $available;
- }
-
- return $available;
+ Router::locateReset();
+ return parent::getAllJsonInfo();
}
public function getAddOnPath($addOnId)
@@ -41,38 +26,6 @@ protected function loadAddOnClass($addOnOrId, array $jsonInfo = null)
return new $class($addOnOrId ?: $addOn->getAddOnId());
}
-
- private function DevHelper_getAvailableAddOnIds()
- {
- $addOnIds = [];
-
- list(, $addOnPaths) = Router::getLocatePaths();
-
- foreach ($addOnPaths as $addOnPath) {
- foreach (new \DirectoryIterator($addOnPath . '/src/addons') as $entry) {
- if (!$this->isValidDir($entry)) {
- continue;
- }
-
- if ($this->isDirAddOnRoot($entry)) {
- $addOnIds[] = $entry->getBasename();
- } else {
- $vendorPrefix = $entry->getBasename();
- foreach (new \DirectoryIterator($entry->getPathname()) as $addOnDir) {
- if (!$this->isValidDir($addOnDir)) {
- continue;
- }
-
- if ($this->isDirAddOnRoot($addOnDir)) {
- $addOnIds[] = "$vendorPrefix/{$addOnDir->getBasename()}";
- }
- }
- }
- }
- }
-
- return $addOnIds;
- }
}
if (false) {
From 1918c2dd20738074fff8ae97637f6ddda1ae572a Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 30 Aug 2017 13:35:03 +0700
Subject: [PATCH 007/118] Improve support for locate (continue from 029fa70e)
---
src/addons/DevHelper/Autoloader.php | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/addons/DevHelper/Autoloader.php b/src/addons/DevHelper/Autoloader.php
index c8d2d3f..4716f7a 100644
--- a/src/addons/DevHelper/Autoloader.php
+++ b/src/addons/DevHelper/Autoloader.php
@@ -23,12 +23,18 @@ public function setupAutoloader()
$autoLoader = require('/var/www/html/xenforo/src/vendor/autoload.php');
list(, $addOnPaths) = Router::getLocatePaths();
- foreach ($addOnPaths as $addOnPath) {
+ foreach ($addOnPaths as $addOnPathSuffix => $addOnPath) {
$globPattern = $addOnPath . '/src/addons/*';
- foreach (glob($globPattern) as $rootPath) {
+ $rootPaths = glob($globPattern);
+ foreach ($rootPaths as $rootPath) {
$rootNamespace = basename($rootPath);
$autoLoader->setPsr4($rootNamespace . '\\', $rootPath);
}
+
+ if (count($rootPaths) === 0) {
+ $rootNamespace = str_replace(DIRECTORY_SEPARATOR, '\\', $addOnPathSuffix);
+ $autoLoader->setPsr4($rootNamespace . '\\', $addOnPath);
+ }
}
spl_autoload_register(array($this, 'autoload'), true, true);
From 6a60923fa352e0f41629ce43fee716fd63562df9 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 7 Sep 2017 00:04:21 +0700
Subject: [PATCH 008/118] Add addons2.txt regeneration
---
src/addons/DevHelper/Router.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/addons/DevHelper/Router.php b/src/addons/DevHelper/Router.php
index 6a20901..8deacf2 100644
--- a/src/addons/DevHelper/Router.php
+++ b/src/addons/DevHelper/Router.php
@@ -151,10 +151,10 @@ public static function getLocatePaths()
$addOnPaths = array();
$txtPath = sprintf('%s/internal_data/addons2.txt', $xenforoDir);
- $lines = array();
- if (file_exists($txtPath)) {
- $lines = file($txtPath);
+ if (!file_exists($txtPath)) {
+ exec('/usr/local/bin/find-addons2.sh');
}
+ $lines = file($txtPath);
$prefix = 'addons';
$prefixLength = strlen($prefix);
From 391000596e1e418beb3cfc45a06ca83d50bff721 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 7 Sep 2017 00:04:48 +0700
Subject: [PATCH 009/118] Simplify add-on release builder
---
.../XF/Service/AddOn/ReleaseBuilder.php | 60 +++----------------
1 file changed, 9 insertions(+), 51 deletions(-)
diff --git a/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
index ae1e25d..671869b 100644
--- a/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
+++ b/src/addons/DevHelper/XF/Service/AddOn/ReleaseBuilder.php
@@ -7,57 +7,6 @@
class ReleaseBuilder extends XFCP_ReleaseBuilder
{
- protected function prepareFilesToCopy()
- {
- parent::prepareFilesToCopy();
-
- $srcUptoNamespace = $this->addOnRoot;
- $srcUptoNamespaceLocated = StreamWrapper::locate($srcUptoNamespace);
- $repoPath = preg_replace('#/src/addons.+$#', '', $srcUptoNamespaceLocated);
- $ds = DIRECTORY_SEPARATOR;
-
- $dirPaths = [];
- foreach (new \DirectoryIterator($repoPath) as $dirCandidate) {
- if (!$dirCandidate->isDir()
- || $dirCandidate->isDot()) {
- continue;
- }
-
- $skip = false;
- $basename = $dirCandidate->getBasename();
- switch ($basename) {
- case '.git':
- case 'addons':
- case 'dev-scripts':
- case 'library':
- case 'src':
- case 'xenforo':
- $skip = true;
- break;
- }
- if ($skip) {
- continue;
- }
-
- $dirPaths[] = $dirCandidate->getPathname();
- }
-
- foreach ($dirPaths as $dirPath) {
- $dirStandardized = $this->standardizePath($repoPath, $dirPath);
-
- $filesIterator = $this->getFileIterator($dirPath);
- foreach ($filesIterator AS $file) {
- if ($file->isDir()) {
- continue;
- }
-
- $fileStandardized = $this->standardizePath($dirPath, $file->getPathname());
- $destination = $this->uploadRoot . $ds . $dirStandardized . $ds . $fileStandardized;
- File::copyFile($file->getPathname(), $destination, false);
- }
- }
- }
-
public function finalizeRelease()
{
$oopsPath = $this->buildRoot . '/oops';
@@ -66,6 +15,15 @@ public function finalizeRelease()
parent::finalizeRelease();
}
+
+ protected function getExcludedDirectories()
+ {
+ $dirs = parent::getExcludedDirectories();
+
+ $dirs[] = '.git';
+
+ return $dirs;
+ }
}
if (false) {
From e8421b3d71d30cb71a6724cfc1e90a86c96ae332 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 21 Sep 2017 19:01:17 +0700
Subject: [PATCH 010/118] Fix http client issue because of our stream wrapper
---
src/addons/DevHelper/Listener.php | 16 ++++++++++++++++
src/addons/DevHelper/XF/Extension.php | 12 ++++++++++++
2 files changed, 28 insertions(+)
create mode 100644 src/addons/DevHelper/Listener.php
diff --git a/src/addons/DevHelper/Listener.php b/src/addons/DevHelper/Listener.php
new file mode 100644
index 0000000..303b7db
--- /dev/null
+++ b/src/addons/DevHelper/Listener.php
@@ -0,0 +1,16 @@
+setDefaultOption('verify', '/var/www/html/xenforo/src/XF/Http/ca-bundle.crt');
+ }
+}
\ No newline at end of file
diff --git a/src/addons/DevHelper/XF/Extension.php b/src/addons/DevHelper/XF/Extension.php
index 54d9925..f81ddcc 100644
--- a/src/addons/DevHelper/XF/Extension.php
+++ b/src/addons/DevHelper/XF/Extension.php
@@ -6,6 +6,16 @@
class Extension extends \XF\_Extension
{
+ /**
+ * Extension constructor.
+ * @param array $listeners
+ * @param array $classExtensions
+ *
+ * @see \DevHelper\Listener::httpClientConfig()
+ * @see \DevHelper\XF\AddOn\AddOn
+ * @see \DevHelper\XF\AddOn\Manager
+ * @see \DevHelper\XF\Service\AddOn\ReleaseBuilder
+ */
public function __construct(array $listeners = [], array $classExtensions = [])
{
parent::__construct($listeners, $classExtensions);
@@ -13,6 +23,8 @@ public function __construct(array $listeners = [], array $classExtensions = [])
stream_wrapper_register(StreamWrapper::PROTOCOL, StreamWrapper::class);
XF::setSourceDirectory(StreamWrapper::PROTOCOL . '://src');
+ $this->addListener('http_client_config', ['DevHelper\\Listener', 'httpClientConfig']);
+
$classes = [
'XF\AddOn\AddOn',
'XF\AddOn\Manager',
From 84042380095b817ce8bf59f3c4b15d474b3e6935 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 21 Sep 2017 19:01:31 +0700
Subject: [PATCH 011/118] Improve Router implementation
---
src/addons/DevHelper/Router.php | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/addons/DevHelper/Router.php b/src/addons/DevHelper/Router.php
index 8deacf2..c8ede29 100644
--- a/src/addons/DevHelper/Router.php
+++ b/src/addons/DevHelper/Router.php
@@ -4,6 +4,8 @@
class Router
{
+ const PATH_SLASH_SRC_ADDONS_SLASH = '/src/addons/';
+
public static function route($routerPhpPath)
{
$_SERVER['DEVHELPER_ROUTER_PHP'] = $routerPhpPath;
@@ -119,12 +121,16 @@ public static function locate($fullPath, &$success = null)
foreach ($addOnPaths as $addOnPathSuffix => $addOnPath) {
$candidatePaths = [];
- if (strpos($shortened, '/src/addons/') === 0) {
- $relativeRegex = '#^.+?' . preg_quote($addOnPathSuffix, '#') . '#';
- $relativePath = preg_replace($relativeRegex, '', $shortened, -1, $count);
- if ($count === 1) {
- $candidatePaths[] = $addOnPath . $shortened;
- $candidatePaths[] = $addOnPath . $relativePath;
+ if (strpos($shortened, static::PATH_SLASH_SRC_ADDONS_SLASH) === 0) {
+ $relativePath = substr($shortened, strlen(static::PATH_SLASH_SRC_ADDONS_SLASH));
+ if (strpos($relativePath, $addOnPathSuffix) === 0) {
+ $candidatePaths[] = $addOnPath . '/' . substr($relativePath, strlen($addOnPathSuffix));
+ } elseif (strpos($addOnPathSuffix, $relativePath) === 0) {
+ $parentPathSuffix = dirname($addOnPathSuffix);
+ $parentPath = dirname($addOnPath);
+ if (strpos($relativePath, $parentPathSuffix) === 0) {
+ $candidatePaths[] = $parentPath;
+ }
}
}
From c778cbe13287dbb274601e999ff991fd9d782eec Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 26 Oct 2017 00:03:43 +0700
Subject: [PATCH 012/118] Clean up
---
.gitignore | 13 -
addons/.gitkeep | 0
aptana.xml | 215 -
.../Lib/CodeMirror/addon/dialog/dialog.css | 32 -
.../Lib/CodeMirror/addon/dialog/dialog.js | 157 -
.../addon/scroll/annotatescrollbar.js | 122 -
.../CodeMirror/addon/scroll/scrollpastend.js | 48 -
.../addon/scroll/simplescrollbars.css | 66 -
.../addon/scroll/simplescrollbars.js | 152 -
.../CodeMirror/addon/search/jump-to-line.js | 49 -
.../addon/search/match-highlighter.js | 165 -
.../addon/search/matchesonscrollbar.css | 8 -
.../addon/search/matchesonscrollbar.js | 97 -
.../Lib/CodeMirror/addon/search/search.js | 252 -
.../CodeMirror/addon/search/searchcursor.js | 289 -
.../Lib/CodeMirror/lib/codemirror.css | 340 -
js/DevHelper/Lib/CodeMirror/lib/codemirror.js | 9479 -----------------
js/DevHelper/Lib/CodeMirror/mode/css/css.js | 830 --
js/DevHelper/Lib/CodeMirror/mode/css/gss.html | 103 -
.../Lib/CodeMirror/mode/css/gss_test.js | 17 -
.../Lib/CodeMirror/mode/css/index.html | 75 -
.../Lib/CodeMirror/mode/css/less.html | 152 -
.../Lib/CodeMirror/mode/css/less_test.js | 54 -
.../Lib/CodeMirror/mode/css/scss.html | 157 -
.../Lib/CodeMirror/mode/css/scss_test.js | 110 -
js/DevHelper/Lib/CodeMirror/mode/css/test.js | 200 -
.../mode/htmlembedded/htmlembedded.js | 28 -
.../CodeMirror/mode/htmlembedded/index.html | 60 -
.../CodeMirror/mode/htmlmixed/htmlmixed.js | 152 -
.../Lib/CodeMirror/mode/htmlmixed/index.html | 100 -
.../Lib/CodeMirror/mode/javascript/index.html | 114 -
.../CodeMirror/mode/javascript/javascript.js | 818 --
.../CodeMirror/mode/javascript/json-ld.html | 72 -
.../Lib/CodeMirror/mode/javascript/test.js | 379 -
.../mode/javascript/typescript.html | 61 -
.../Lib/CodeMirror/mode/xml/index.html | 61 -
js/DevHelper/Lib/CodeMirror/mode/xml/test.js | 51 -
js/DevHelper/Lib/CodeMirror/mode/xml/xml.js | 394 -
js/DevHelper/full/template_edit.js | 132 -
library/DevHelper/Autoloader.php | 175 -
library/DevHelper/Config/Base.php | 415 -
library/DevHelper/ControllerHelper/AddOn.php | 65 -
library/DevHelper/Generator/Code/Common.php | 233 -
.../Generator/Code/ControllerAdmin.php | 724 --
.../DevHelper/Generator/Code/DataWriter.php | 395 -
.../DevHelper/Generator/Code/Installer.php | 133 -
library/DevHelper/Generator/Code/Listener.php | 111 -
library/DevHelper/Generator/Code/Model.php | 674 --
.../Generator/Code/RoutePrefixAdmin.php | 131 -
.../Generator/Code/XenForoConfig.php | 55 -
library/DevHelper/Generator/Db.php | 360 -
library/DevHelper/Generator/File.php | 747 --
library/DevHelper/Generator/Phrase.php | 64 -
library/DevHelper/Generator/Template.php | 70 -
library/DevHelper/Generator/phpcs.xml | 28 -
library/DevHelper/Helper/File.php | 34 -
library/DevHelper/Helper/Js.php | 83 -
library/DevHelper/Helper/Php.php | 107 -
library/DevHelper/Helper/Phrase.php | 149 -
library/DevHelper/Helper/ShippableHelper.php | 85 -
.../Helper/ShippableHelper/Crypt.php | 153 -
.../Helper/ShippableHelper/DateTime.php | 99 -
.../DevHelper/Helper/ShippableHelper/Html.php | 399 -
.../DevHelper/Helper/ShippableHelper/Http.php | 110 -
.../Helper/ShippableHelper/Image.php | 77 -
.../Helper/ShippableHelper/ImageCore.php | 330 -
.../Helper/ShippableHelper/ImageSize.php | 542 -
.../DevHelper/Helper/ShippableHelper/S3.php | 227 -
.../Helper/ShippableHelper/String.php | 26 -
.../Helper/ShippableHelper/TempFile.php | 213 -
.../Helper/ShippableHelper/Updater.php | 195 -
.../Helper/ShippableHelper/UpdaterCore.php | 794 --
library/DevHelper/Helper/Template.php | 56 -
library/DevHelper/Helper/Xfcp.php | 96 -
library/DevHelper/Installer.php | 107 -
library/DevHelper/Listener.php | 175 -
library/DevHelper/Model/Config.php | 48 -
library/DevHelper/Router.php | 154 -
.../DevHelper/ViewAdmin/AddOn/DataManager.php | 18 -
library/DevHelper/XenForo/CodeEvent.php | 113 -
.../XenForo/ControllerAdmin/AddOn.php | 387 -
.../AdminTemplateModification.php | 21 -
.../ControllerAdmin/CodeEventListener.php | 69 -
.../XenForo/ControllerAdmin/Home.php | 13 -
.../XenForo/ControllerAdmin/Permission.php | 41 -
.../XenForo/ControllerAdmin/Phrase.php | 28 -
.../XenForo/ControllerAdmin/RoutePrefix.php | 26 -
.../XenForo/ControllerAdmin/Template.php | 28 -
.../ControllerAdmin/TemplateModification.php | 21 -
.../XenForo/ControllerAdmin/Tools.php | 190 -
.../XenForo/ControllerHelper/Editor.php | 26 -
.../XenForo/ControllerPublic/Forum.php | 43 -
.../XenForo/ControllerPublic/Thread.php | 38 -
.../DevHelper/XenForo/DataWriter/AddOn.php | 15 -
.../XenForo/DataWriter/CodeEventListener.php | 29 -
.../DevHelper/XenForo/DataWriter/Template.php | 24 -
library/DevHelper/XenForo/Debug.php | 52 -
library/DevHelper/XenForo/Model/AddOn.php | 34 -
.../XenForo/Route/PrefixAdmin/Templates.php | 22 -
.../DevHelper/XenForo/Template/Abstract.php | 31 -
.../XenForo/ViewAdmin/AddOn/Upgrade.php | 20 -
.../DevHelper/XenForo/ViewRenderer/Json.php | 71 -
library/DevHelper/addon-devHelper.xml | 262 -
xenforo/.gitkeep | 0
104 files changed, 25865 deletions(-)
create mode 100644 addons/.gitkeep
delete mode 100644 aptana.xml
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.css
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/scroll/annotatescrollbar.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/scroll/scrollpastend.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.css
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/jump-to-line.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/match-highlighter.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.css
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/search.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/addon/search/searchcursor.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/lib/codemirror.css
delete mode 100644 js/DevHelper/Lib/CodeMirror/lib/codemirror.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/css.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/gss.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/gss_test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/index.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/less.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/less_test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/scss.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/scss_test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/css/test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/htmlembedded/htmlembedded.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/htmlembedded/index.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/htmlmixed/htmlmixed.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/htmlmixed/index.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/javascript/index.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/javascript/javascript.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/javascript/json-ld.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/javascript/test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/javascript/typescript.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/xml/index.html
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/xml/test.js
delete mode 100644 js/DevHelper/Lib/CodeMirror/mode/xml/xml.js
delete mode 100755 js/DevHelper/full/template_edit.js
delete mode 100644 library/DevHelper/Autoloader.php
delete mode 100755 library/DevHelper/Config/Base.php
delete mode 100644 library/DevHelper/ControllerHelper/AddOn.php
delete mode 100755 library/DevHelper/Generator/Code/Common.php
delete mode 100755 library/DevHelper/Generator/Code/ControllerAdmin.php
delete mode 100755 library/DevHelper/Generator/Code/DataWriter.php
delete mode 100755 library/DevHelper/Generator/Code/Installer.php
delete mode 100644 library/DevHelper/Generator/Code/Listener.php
delete mode 100755 library/DevHelper/Generator/Code/Model.php
delete mode 100755 library/DevHelper/Generator/Code/RoutePrefixAdmin.php
delete mode 100644 library/DevHelper/Generator/Code/XenForoConfig.php
delete mode 100755 library/DevHelper/Generator/Db.php
delete mode 100755 library/DevHelper/Generator/File.php
delete mode 100755 library/DevHelper/Generator/Phrase.php
delete mode 100755 library/DevHelper/Generator/Template.php
delete mode 100644 library/DevHelper/Generator/phpcs.xml
delete mode 100644 library/DevHelper/Helper/File.php
delete mode 100644 library/DevHelper/Helper/Js.php
delete mode 100644 library/DevHelper/Helper/Php.php
delete mode 100644 library/DevHelper/Helper/Phrase.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/Crypt.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/DateTime.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/Html.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/Http.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/Image.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/ImageCore.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/ImageSize.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/S3.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/String.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/TempFile.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/Updater.php
delete mode 100644 library/DevHelper/Helper/ShippableHelper/UpdaterCore.php
delete mode 100644 library/DevHelper/Helper/Template.php
delete mode 100644 library/DevHelper/Helper/Xfcp.php
delete mode 100644 library/DevHelper/Installer.php
delete mode 100755 library/DevHelper/Listener.php
delete mode 100755 library/DevHelper/Model/Config.php
delete mode 100644 library/DevHelper/Router.php
delete mode 100755 library/DevHelper/ViewAdmin/AddOn/DataManager.php
delete mode 100644 library/DevHelper/XenForo/CodeEvent.php
delete mode 100755 library/DevHelper/XenForo/ControllerAdmin/AddOn.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/AdminTemplateModification.php
delete mode 100755 library/DevHelper/XenForo/ControllerAdmin/CodeEventListener.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/Home.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/Permission.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/Phrase.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/RoutePrefix.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/Template.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/TemplateModification.php
delete mode 100644 library/DevHelper/XenForo/ControllerAdmin/Tools.php
delete mode 100644 library/DevHelper/XenForo/ControllerHelper/Editor.php
delete mode 100644 library/DevHelper/XenForo/ControllerPublic/Forum.php
delete mode 100644 library/DevHelper/XenForo/ControllerPublic/Thread.php
delete mode 100644 library/DevHelper/XenForo/DataWriter/AddOn.php
delete mode 100644 library/DevHelper/XenForo/DataWriter/CodeEventListener.php
delete mode 100644 library/DevHelper/XenForo/DataWriter/Template.php
delete mode 100644 library/DevHelper/XenForo/Debug.php
delete mode 100755 library/DevHelper/XenForo/Model/AddOn.php
delete mode 100644 library/DevHelper/XenForo/Route/PrefixAdmin/Templates.php
delete mode 100644 library/DevHelper/XenForo/Template/Abstract.php
delete mode 100644 library/DevHelper/XenForo/ViewAdmin/AddOn/Upgrade.php
delete mode 100644 library/DevHelper/XenForo/ViewRenderer/Json.php
delete mode 100644 library/DevHelper/addon-devHelper.xml
create mode 100644 xenforo/.gitkeep
diff --git a/.gitignore b/.gitignore
index abfb9be..e69de29 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +0,0 @@
-.DS_Store
-*.devhelper
-library/DevHelper/DevHelper/Config.php
-library/DevHelper/DevHelper/Generated/
-FileSums.php
-js/DevHelper/CodeMirror/mode/css/scss_test.js
-js/DevHelper/CodeMirror/mode/css/test.js
-js/DevHelper/CodeMirror/mode/javascript/test.js
-library/DevHelper/DevHelper/XFCP
-library/DevHelper/Config.php
-.data
-docker-compose.override.yml
-js/DevHelper/*.min.js
diff --git a/addons/.gitkeep b/addons/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/aptana.xml b/aptana.xml
deleted file mode 100644
index d94a118..0000000
--- a/aptana.xml
+++ /dev/null
@@ -1,215 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.css b/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.css
deleted file mode 100644
index 677c078..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.CodeMirror-dialog {
- position: absolute;
- left: 0; right: 0;
- background: inherit;
- z-index: 15;
- padding: .1em .8em;
- overflow: hidden;
- color: inherit;
-}
-
-.CodeMirror-dialog-top {
- border-bottom: 1px solid #eee;
- top: 0;
-}
-
-.CodeMirror-dialog-bottom {
- border-top: 1px solid #eee;
- bottom: 0;
-}
-
-.CodeMirror-dialog input {
- border: none;
- outline: none;
- background: transparent;
- width: 20em;
- color: inherit;
- font-family: monospace;
-}
-
-.CodeMirror-dialog button {
- font-size: 70%;
-}
diff --git a/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.js b/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.js
deleted file mode 100644
index f10bb5b..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/dialog/dialog.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Open simple dialogs on top of an editor. Relies on dialog.css.
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- function dialogDiv(cm, template, bottom) {
- var wrap = cm.getWrapperElement();
- var dialog;
- dialog = wrap.appendChild(document.createElement("div"));
- if (bottom)
- dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
- else
- dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
-
- if (typeof template == "string") {
- dialog.innerHTML = template;
- } else { // Assuming it's a detached DOM element.
- dialog.appendChild(template);
- }
- return dialog;
- }
-
- function closeNotification(cm, newVal) {
- if (cm.state.currentNotificationClose)
- cm.state.currentNotificationClose();
- cm.state.currentNotificationClose = newVal;
- }
-
- CodeMirror.defineExtension("openDialog", function(template, callback, options) {
- if (!options) options = {};
-
- closeNotification(this, null);
-
- var dialog = dialogDiv(this, template, options.bottom);
- var closed = false, me = this;
- function close(newVal) {
- if (typeof newVal == 'string') {
- inp.value = newVal;
- } else {
- if (closed) return;
- closed = true;
- dialog.parentNode.removeChild(dialog);
- me.focus();
-
- if (options.onClose) options.onClose(dialog);
- }
- }
-
- var inp = dialog.getElementsByTagName("input")[0], button;
- if (inp) {
- inp.focus();
-
- if (options.value) {
- inp.value = options.value;
- if (options.selectValueOnOpen !== false) {
- inp.select();
- }
- }
-
- if (options.onInput)
- CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
- if (options.onKeyUp)
- CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
-
- CodeMirror.on(inp, "keydown", function(e) {
- if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
- if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
- inp.blur();
- CodeMirror.e_stop(e);
- close();
- }
- if (e.keyCode == 13) callback(inp.value, e);
- });
-
- if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
- } else if (button = dialog.getElementsByTagName("button")[0]) {
- CodeMirror.on(button, "click", function() {
- close();
- me.focus();
- });
-
- if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
-
- button.focus();
- }
- return close;
- });
-
- CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
- closeNotification(this, null);
- var dialog = dialogDiv(this, template, options && options.bottom);
- var buttons = dialog.getElementsByTagName("button");
- var closed = false, me = this, blurring = 1;
- function close() {
- if (closed) return;
- closed = true;
- dialog.parentNode.removeChild(dialog);
- me.focus();
- }
- buttons[0].focus();
- for (var i = 0; i < buttons.length; ++i) {
- var b = buttons[i];
- (function(callback) {
- CodeMirror.on(b, "click", function(e) {
- CodeMirror.e_preventDefault(e);
- close();
- if (callback) callback(me);
- });
- })(callbacks[i]);
- CodeMirror.on(b, "blur", function() {
- --blurring;
- setTimeout(function() { if (blurring <= 0) close(); }, 200);
- });
- CodeMirror.on(b, "focus", function() { ++blurring; });
- }
- });
-
- /*
- * openNotification
- * Opens a notification, that can be closed with an optional timer
- * (default 5000ms timer) and always closes on click.
- *
- * If a notification is opened while another is opened, it will close the
- * currently opened one and open the new one immediately.
- */
- CodeMirror.defineExtension("openNotification", function(template, options) {
- closeNotification(this, close);
- var dialog = dialogDiv(this, template, options && options.bottom);
- var closed = false, doneTimer;
- var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
-
- function close() {
- if (closed) return;
- closed = true;
- clearTimeout(doneTimer);
- dialog.parentNode.removeChild(dialog);
- }
-
- CodeMirror.on(dialog, 'click', function(e) {
- CodeMirror.e_preventDefault(e);
- close();
- });
-
- if (duration)
- doneTimer = setTimeout(close, duration);
-
- return close;
- });
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/scroll/annotatescrollbar.js b/js/DevHelper/Lib/CodeMirror/addon/scroll/annotatescrollbar.js
deleted file mode 100644
index f2276fc..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/scroll/annotatescrollbar.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- CodeMirror.defineExtension("annotateScrollbar", function(options) {
- if (typeof options == "string") options = {className: options};
- return new Annotation(this, options);
- });
-
- CodeMirror.defineOption("scrollButtonHeight", 0);
-
- function Annotation(cm, options) {
- this.cm = cm;
- this.options = options;
- this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
- this.annotations = [];
- this.doRedraw = this.doUpdate = null;
- this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
- this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
- this.computeScale();
-
- function scheduleRedraw(delay) {
- clearTimeout(self.doRedraw);
- self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
- }
-
- var self = this;
- cm.on("refresh", this.resizeHandler = function() {
- clearTimeout(self.doUpdate);
- self.doUpdate = setTimeout(function() {
- if (self.computeScale()) scheduleRedraw(20);
- }, 100);
- });
- cm.on("markerAdded", this.resizeHandler);
- cm.on("markerCleared", this.resizeHandler);
- if (options.listenForChanges !== false)
- cm.on("change", this.changeHandler = function() {
- scheduleRedraw(250);
- });
- }
-
- Annotation.prototype.computeScale = function() {
- var cm = this.cm;
- var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
- cm.getScrollerElement().scrollHeight
- if (hScale != this.hScale) {
- this.hScale = hScale;
- return true;
- }
- };
-
- Annotation.prototype.update = function(annotations) {
- this.annotations = annotations;
- this.redraw();
- };
-
- Annotation.prototype.redraw = function(compute) {
- if (compute !== false) this.computeScale();
- var cm = this.cm, hScale = this.hScale;
-
- var frag = document.createDocumentFragment(), anns = this.annotations;
-
- var wrapping = cm.getOption("lineWrapping");
- var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
- var curLine = null, curLineObj = null;
- function getY(pos, top) {
- if (curLine != pos.line) {
- curLine = pos.line;
- curLineObj = cm.getLineHandle(curLine);
- }
- if ((curLineObj.widgets && curLineObj.widgets.length) ||
- (wrapping && curLineObj.height > singleLineH))
- return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
- var topY = cm.heightAtLine(curLineObj, "local");
- return topY + (top ? 0 : curLineObj.height);
- }
-
- var lastLine = cm.lastLine()
- if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
- var ann = anns[i];
- if (ann.to.line > lastLine) continue;
- var top = nextTop || getY(ann.from, true) * hScale;
- var bottom = getY(ann.to, false) * hScale;
- while (i < anns.length - 1) {
- if (anns[i + 1].to.line > lastLine) break;
- nextTop = getY(anns[i + 1].from, true) * hScale;
- if (nextTop > bottom + .9) break;
- ann = anns[++i];
- bottom = getY(ann.to, false) * hScale;
- }
- if (bottom == top) continue;
- var height = Math.max(bottom - top, 3);
-
- var elt = frag.appendChild(document.createElement("div"));
- elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
- + (top + this.buttonHeight) + "px; height: " + height + "px";
- elt.className = this.options.className;
- if (ann.id) {
- elt.setAttribute("annotation-id", ann.id);
- }
- }
- this.div.textContent = "";
- this.div.appendChild(frag);
- };
-
- Annotation.prototype.clear = function() {
- this.cm.off("refresh", this.resizeHandler);
- this.cm.off("markerAdded", this.resizeHandler);
- this.cm.off("markerCleared", this.resizeHandler);
- if (this.changeHandler) this.cm.off("change", this.changeHandler);
- this.div.parentNode.removeChild(this.div);
- };
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/scroll/scrollpastend.js b/js/DevHelper/Lib/CodeMirror/addon/scroll/scrollpastend.js
deleted file mode 100644
index a2ed089..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/scroll/scrollpastend.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init) {
- cm.off("change", onChange);
- cm.off("refresh", updateBottomMargin);
- cm.display.lineSpace.parentNode.style.paddingBottom = "";
- cm.state.scrollPastEndPadding = null;
- }
- if (val) {
- cm.on("change", onChange);
- cm.on("refresh", updateBottomMargin);
- updateBottomMargin(cm);
- }
- });
-
- function onChange(cm, change) {
- if (CodeMirror.changeEnd(change).line == cm.lastLine())
- updateBottomMargin(cm);
- }
-
- function updateBottomMargin(cm) {
- var padding = "";
- if (cm.lineCount() > 1) {
- var totalH = cm.display.scroller.clientHeight - 30,
- lastLineH = cm.getLineHandle(cm.lastLine()).height;
- padding = (totalH - lastLineH) + "px";
- }
- if (cm.state.scrollPastEndPadding != padding) {
- cm.state.scrollPastEndPadding = padding;
- cm.display.lineSpace.parentNode.style.paddingBottom = padding;
- cm.off("refresh", updateBottomMargin);
- cm.setSize();
- cm.on("refresh", updateBottomMargin);
- }
- }
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.css b/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.css
deleted file mode 100644
index 5eea7aa..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.css
+++ /dev/null
@@ -1,66 +0,0 @@
-.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {
- position: absolute;
- background: #ccc;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- border: 1px solid #bbb;
- border-radius: 2px;
-}
-
-.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {
- position: absolute;
- z-index: 6;
- background: #eee;
-}
-
-.CodeMirror-simplescroll-horizontal {
- bottom: 0; left: 0;
- height: 8px;
-}
-.CodeMirror-simplescroll-horizontal div {
- bottom: 0;
- height: 100%;
-}
-
-.CodeMirror-simplescroll-vertical {
- right: 0; top: 0;
- width: 8px;
-}
-.CodeMirror-simplescroll-vertical div {
- right: 0;
- width: 100%;
-}
-
-
-.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {
- display: none;
-}
-
-.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
- position: absolute;
- background: #bcd;
- border-radius: 3px;
-}
-
-.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {
- position: absolute;
- z-index: 6;
-}
-
-.CodeMirror-overlayscroll-horizontal {
- bottom: 0; left: 0;
- height: 6px;
-}
-.CodeMirror-overlayscroll-horizontal div {
- bottom: 0;
- height: 100%;
-}
-
-.CodeMirror-overlayscroll-vertical {
- right: 0; top: 0;
- width: 6px;
-}
-.CodeMirror-overlayscroll-vertical div {
- right: 0;
- width: 100%;
-}
diff --git a/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.js b/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.js
deleted file mode 100644
index 23f3e03..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/scroll/simplescrollbars.js
+++ /dev/null
@@ -1,152 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- function Bar(cls, orientation, scroll) {
- this.orientation = orientation;
- this.scroll = scroll;
- this.screen = this.total = this.size = 1;
- this.pos = 0;
-
- this.node = document.createElement("div");
- this.node.className = cls + "-" + orientation;
- this.inner = this.node.appendChild(document.createElement("div"));
-
- var self = this;
- CodeMirror.on(this.inner, "mousedown", function(e) {
- if (e.which != 1) return;
- CodeMirror.e_preventDefault(e);
- var axis = self.orientation == "horizontal" ? "pageX" : "pageY";
- var start = e[axis], startpos = self.pos;
- function done() {
- CodeMirror.off(document, "mousemove", move);
- CodeMirror.off(document, "mouseup", done);
- }
- function move(e) {
- if (e.which != 1) return done();
- self.moveTo(startpos + (e[axis] - start) * (self.total / self.size));
- }
- CodeMirror.on(document, "mousemove", move);
- CodeMirror.on(document, "mouseup", done);
- });
-
- CodeMirror.on(this.node, "click", function(e) {
- CodeMirror.e_preventDefault(e);
- var innerBox = self.inner.getBoundingClientRect(), where;
- if (self.orientation == "horizontal")
- where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0;
- else
- where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0;
- self.moveTo(self.pos + where * self.screen);
- });
-
- function onWheel(e) {
- var moved = CodeMirror.wheelEventPixels(e)[self.orientation == "horizontal" ? "x" : "y"];
- var oldPos = self.pos;
- self.moveTo(self.pos + moved);
- if (self.pos != oldPos) CodeMirror.e_preventDefault(e);
- }
- CodeMirror.on(this.node, "mousewheel", onWheel);
- CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
- }
-
- Bar.prototype.setPos = function(pos, force) {
- if (pos < 0) pos = 0;
- if (pos > this.total - this.screen) pos = this.total - this.screen;
- if (!force && pos == this.pos) return false;
- this.pos = pos;
- this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
- (pos * (this.size / this.total)) + "px";
- return true
- };
-
- Bar.prototype.moveTo = function(pos) {
- if (this.setPos(pos)) this.scroll(pos, this.orientation);
- }
-
- var minButtonSize = 10;
-
- Bar.prototype.update = function(scrollSize, clientSize, barSize) {
- var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize
- if (sizeChanged) {
- this.screen = clientSize;
- this.total = scrollSize;
- this.size = barSize;
- }
-
- var buttonSize = this.screen * (this.size / this.total);
- if (buttonSize < minButtonSize) {
- this.size -= minButtonSize - buttonSize;
- buttonSize = minButtonSize;
- }
- this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
- buttonSize + "px";
- this.setPos(this.pos, sizeChanged);
- };
-
- function SimpleScrollbars(cls, place, scroll) {
- this.addClass = cls;
- this.horiz = new Bar(cls, "horizontal", scroll);
- place(this.horiz.node);
- this.vert = new Bar(cls, "vertical", scroll);
- place(this.vert.node);
- this.width = null;
- }
-
- SimpleScrollbars.prototype.update = function(measure) {
- if (this.width == null) {
- var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle;
- if (style) this.width = parseInt(style.height);
- }
- var width = this.width || 0;
-
- var needsH = measure.scrollWidth > measure.clientWidth + 1;
- var needsV = measure.scrollHeight > measure.clientHeight + 1;
- this.vert.node.style.display = needsV ? "block" : "none";
- this.horiz.node.style.display = needsH ? "block" : "none";
-
- if (needsV) {
- this.vert.update(measure.scrollHeight, measure.clientHeight,
- measure.viewHeight - (needsH ? width : 0));
- this.vert.node.style.bottom = needsH ? width + "px" : "0";
- }
- if (needsH) {
- this.horiz.update(measure.scrollWidth, measure.clientWidth,
- measure.viewWidth - (needsV ? width : 0) - measure.barLeft);
- this.horiz.node.style.right = needsV ? width + "px" : "0";
- this.horiz.node.style.left = measure.barLeft + "px";
- }
-
- return {right: needsV ? width : 0, bottom: needsH ? width : 0};
- };
-
- SimpleScrollbars.prototype.setScrollTop = function(pos) {
- this.vert.setPos(pos);
- };
-
- SimpleScrollbars.prototype.setScrollLeft = function(pos) {
- this.horiz.setPos(pos);
- };
-
- SimpleScrollbars.prototype.clear = function() {
- var parent = this.horiz.node.parentNode;
- parent.removeChild(this.horiz.node);
- parent.removeChild(this.vert.node);
- };
-
- CodeMirror.scrollbarModel.simple = function(place, scroll) {
- return new SimpleScrollbars("CodeMirror-simplescroll", place, scroll);
- };
- CodeMirror.scrollbarModel.overlay = function(place, scroll) {
- return new SimpleScrollbars("CodeMirror-overlayscroll", place, scroll);
- };
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/jump-to-line.js b/js/DevHelper/Lib/CodeMirror/addon/search/jump-to-line.js
deleted file mode 100644
index 8b599cb..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/jump-to-line.js
+++ /dev/null
@@ -1,49 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Defines jumpToLine command. Uses dialog.js if present.
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("../dialog/dialog"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "../dialog/dialog"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- function dialog(cm, text, shortText, deflt, f) {
- if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
- else f(prompt(shortText, deflt));
- }
-
- var jumpDialog =
- 'Jump to line: (Use line:column or scroll% syntax) ';
-
- function interpretLine(cm, string) {
- var num = Number(string)
- if (/^[-+]/.test(string)) return cm.getCursor().line + num
- else return num - 1
- }
-
- CodeMirror.commands.jumpToLine = function(cm) {
- var cur = cm.getCursor();
- dialog(cm, jumpDialog, "Jump to line:", (cur.line + 1) + ":" + cur.ch, function(posStr) {
- if (!posStr) return;
-
- var match;
- if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) {
- cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))
- } else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) {
- var line = Math.round(cm.lineCount() * Number(match[1]) / 100);
- if (/^[-+]/.test(match[1])) line = cur.line + line + 1;
- cm.setCursor(line - 1, cur.ch);
- } else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) {
- cm.setCursor(interpretLine(cm, match[1]), cur.ch);
- }
- });
- };
-
- CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine";
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/match-highlighter.js b/js/DevHelper/Lib/CodeMirror/addon/search/match-highlighter.js
deleted file mode 100644
index 73ba0e0..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/match-highlighter.js
+++ /dev/null
@@ -1,165 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Highlighting text that matches the selection
-//
-// Defines an option highlightSelectionMatches, which, when enabled,
-// will style strings that match the selection throughout the
-// document.
-//
-// The option can be set to true to simply enable it, or to a
-// {minChars, style, wordsOnly, showToken, delay} object to explicitly
-// configure it. minChars is the minimum amount of characters that should be
-// selected for the behavior to occur, and style is the token style to
-// apply to the matches. This will be prefixed by "cm-" to create an
-// actual CSS class name. If wordsOnly is enabled, the matches will be
-// highlighted only if the selected text is a word. showToken, when enabled,
-// will cause the current token to be highlighted when nothing is selected.
-// delay is used to specify how much time to wait, in milliseconds, before
-// highlighting the matches. If annotateScrollbar is enabled, the occurences
-// will be highlighted on the scrollbar via the matchesonscrollbar addon.
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- var defaults = {
- style: "matchhighlight",
- minChars: 2,
- delay: 100,
- wordsOnly: false,
- annotateScrollbar: false,
- showToken: false,
- trim: true
- }
-
- function State(options) {
- this.options = {}
- for (var name in defaults)
- this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
- this.overlay = this.timeout = null;
- this.matchesonscroll = null;
- this.active = false;
- }
-
- CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init) {
- removeOverlay(cm);
- clearTimeout(cm.state.matchHighlighter.timeout);
- cm.state.matchHighlighter = null;
- cm.off("cursorActivity", cursorActivity);
- cm.off("focus", onFocus)
- }
- if (val) {
- var state = cm.state.matchHighlighter = new State(val);
- if (cm.hasFocus()) {
- state.active = true
- highlightMatches(cm)
- } else {
- cm.on("focus", onFocus)
- }
- cm.on("cursorActivity", cursorActivity);
- }
- });
-
- function cursorActivity(cm) {
- var state = cm.state.matchHighlighter;
- if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
- }
-
- function onFocus(cm) {
- var state = cm.state.matchHighlighter
- if (!state.active) {
- state.active = true
- scheduleHighlight(cm, state)
- }
- }
-
- function scheduleHighlight(cm, state) {
- clearTimeout(state.timeout);
- state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
- }
-
- function addOverlay(cm, query, hasBoundary, style) {
- var state = cm.state.matchHighlighter;
- cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
- if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
- var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
- state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
- {className: "CodeMirror-selection-highlight-scrollbar"});
- }
- }
-
- function removeOverlay(cm) {
- var state = cm.state.matchHighlighter;
- if (state.overlay) {
- cm.removeOverlay(state.overlay);
- state.overlay = null;
- if (state.matchesonscroll) {
- state.matchesonscroll.clear();
- state.matchesonscroll = null;
- }
- }
- }
-
- function highlightMatches(cm) {
- cm.operation(function() {
- var state = cm.state.matchHighlighter;
- removeOverlay(cm);
- if (!cm.somethingSelected() && state.options.showToken) {
- var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken;
- var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
- while (start && re.test(line.charAt(start - 1))) --start;
- while (end < line.length && re.test(line.charAt(end))) ++end;
- if (start < end)
- addOverlay(cm, line.slice(start, end), re, state.options.style);
- return;
- }
- var from = cm.getCursor("from"), to = cm.getCursor("to");
- if (from.line != to.line) return;
- if (state.options.wordsOnly && !isWord(cm, from, to)) return;
- var selection = cm.getRange(from, to)
- if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "")
- if (selection.length >= state.options.minChars)
- addOverlay(cm, selection, false, state.options.style);
- });
- }
-
- function isWord(cm, from, to) {
- var str = cm.getRange(from, to);
- if (str.match(/^\w+$/) !== null) {
- if (from.ch > 0) {
- var pos = {line: from.line, ch: from.ch - 1};
- var chr = cm.getRange(pos, from);
- if (chr.match(/\W/) === null) return false;
- }
- if (to.ch < cm.getLine(from.line).length) {
- var pos = {line: to.line, ch: to.ch + 1};
- var chr = cm.getRange(to, pos);
- if (chr.match(/\W/) === null) return false;
- }
- return true;
- } else return false;
- }
-
- function boundariesAround(stream, re) {
- return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
- (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
- }
-
- function makeOverlay(query, hasBoundary, style) {
- return {token: function(stream) {
- if (stream.match(query) &&
- (!hasBoundary || boundariesAround(stream, hasBoundary)))
- return style;
- stream.next();
- stream.skipTo(query.charAt(0)) || stream.skipToEnd();
- }};
- }
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.css b/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.css
deleted file mode 100644
index 77932cc..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.css
+++ /dev/null
@@ -1,8 +0,0 @@
-.CodeMirror-search-match {
- background: gold;
- border-top: 1px solid orange;
- border-bottom: 1px solid orange;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- opacity: .5;
-}
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.js b/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.js
deleted file mode 100644
index 8d19228..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/matchesonscrollbar.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
- if (typeof options == "string") options = {className: options};
- if (!options) options = {};
- return new SearchAnnotation(this, query, caseFold, options);
- });
-
- function SearchAnnotation(cm, query, caseFold, options) {
- this.cm = cm;
- this.options = options;
- var annotateOptions = {listenForChanges: false};
- for (var prop in options) annotateOptions[prop] = options[prop];
- if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
- this.annotation = cm.annotateScrollbar(annotateOptions);
- this.query = query;
- this.caseFold = caseFold;
- this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
- this.matches = [];
- this.update = null;
-
- this.findMatches();
- this.annotation.update(this.matches);
-
- var self = this;
- cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
- }
-
- var MAX_MATCHES = 1000;
-
- SearchAnnotation.prototype.findMatches = function() {
- if (!this.gap) return;
- for (var i = 0; i < this.matches.length; i++) {
- var match = this.matches[i];
- if (match.from.line >= this.gap.to) break;
- if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
- }
- var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
- var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
- while (cursor.findNext()) {
- var match = {from: cursor.from(), to: cursor.to()};
- if (match.from.line >= this.gap.to) break;
- this.matches.splice(i++, 0, match);
- if (this.matches.length > maxMatches) break;
- }
- this.gap = null;
- };
-
- function offsetLine(line, changeStart, sizeChange) {
- if (line <= changeStart) return line;
- return Math.max(changeStart, line + sizeChange);
- }
-
- SearchAnnotation.prototype.onChange = function(change) {
- var startLine = change.from.line;
- var endLine = CodeMirror.changeEnd(change).line;
- var sizeChange = endLine - change.to.line;
- if (this.gap) {
- this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
- this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
- } else {
- this.gap = {from: change.from.line, to: endLine + 1};
- }
-
- if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
- var match = this.matches[i];
- var newFrom = offsetLine(match.from.line, startLine, sizeChange);
- if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
- var newTo = offsetLine(match.to.line, startLine, sizeChange);
- if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
- }
- clearTimeout(this.update);
- var self = this;
- this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
- };
-
- SearchAnnotation.prototype.updateAfterChange = function() {
- this.findMatches();
- this.annotation.update(this.matches);
- };
-
- SearchAnnotation.prototype.clear = function() {
- this.cm.off("change", this.changeHandler);
- this.annotation.clear();
- };
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/search.js b/js/DevHelper/Lib/CodeMirror/addon/search/search.js
deleted file mode 100644
index 236e54c..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/search.js
+++ /dev/null
@@ -1,252 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Define search commands. Depends on dialog.js or another
-// implementation of the openDialog method.
-
-// Replace works a little oddly -- it will do the replace on the next
-// Ctrl-G (or whatever is bound to findNext) press. You prevent a
-// replace by making sure the match is no longer selected when hitting
-// Ctrl-G.
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- function searchOverlay(query, caseInsensitive) {
- if (typeof query == "string")
- query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
- else if (!query.global)
- query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
-
- return {token: function(stream) {
- query.lastIndex = stream.pos;
- var match = query.exec(stream.string);
- if (match && match.index == stream.pos) {
- stream.pos += match[0].length || 1;
- return "searching";
- } else if (match) {
- stream.pos = match.index;
- } else {
- stream.skipToEnd();
- }
- }};
- }
-
- function SearchState() {
- this.posFrom = this.posTo = this.lastQuery = this.query = null;
- this.overlay = null;
- }
-
- function getSearchState(cm) {
- return cm.state.search || (cm.state.search = new SearchState());
- }
-
- function queryCaseInsensitive(query) {
- return typeof query == "string" && query == query.toLowerCase();
- }
-
- function getSearchCursor(cm, query, pos) {
- // Heuristic: if the query string is all lowercase, do a case insensitive search.
- return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
- }
-
- function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
- cm.openDialog(text, onEnter, {
- value: deflt,
- selectValueOnOpen: true,
- closeOnEnter: false,
- onClose: function() { clearSearch(cm); },
- onKeyDown: onKeyDown
- });
- }
-
- function dialog(cm, text, shortText, deflt, f) {
- if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
- else f(prompt(shortText, deflt));
- }
-
- function confirmDialog(cm, text, shortText, fs) {
- if (cm.openConfirm) cm.openConfirm(text, fs);
- else if (confirm(shortText)) fs[0]();
- }
-
- function parseString(string) {
- return string.replace(/\\(.)/g, function(_, ch) {
- if (ch == "n") return "\n"
- if (ch == "r") return "\r"
- return ch
- })
- }
-
- function parseQuery(query) {
- var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
- if (isRE) {
- try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
- catch(e) {} // Not a regular expression after all, do a string search
- } else {
- query = parseString(query)
- }
- if (typeof query == "string" ? query == "" : query.test(""))
- query = /x^/;
- return query;
- }
-
- var queryDialog =
- 'Search: (Use /re/ syntax for regexp search) ';
-
- function startSearch(cm, state, query) {
- state.queryText = query;
- state.query = parseQuery(query);
- cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
- state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
- cm.addOverlay(state.overlay);
- if (cm.showMatchesOnScrollbar) {
- if (state.annotate) { state.annotate.clear(); state.annotate = null; }
- state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
- }
- }
-
- function doSearch(cm, rev, persistent, immediate) {
- var state = getSearchState(cm);
- if (state.query) return findNext(cm, rev);
- var q = cm.getSelection() || state.lastQuery;
- if (persistent && cm.openDialog) {
- var hiding = null
- var searchNext = function(query, event) {
- CodeMirror.e_stop(event);
- if (!query) return;
- if (query != state.queryText) {
- startSearch(cm, state, query);
- state.posFrom = state.posTo = cm.getCursor();
- }
- if (hiding) hiding.style.opacity = 1
- findNext(cm, event.shiftKey, function(_, to) {
- var dialog
- if (to.line < 3 && document.querySelector &&
- (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
- dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
- (hiding = dialog).style.opacity = .4
- })
- };
- persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
- var keyName = CodeMirror.keyName(event)
- var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
- if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
- if (cmd == "findNext" || cmd == "findPrev" ||
- cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
- CodeMirror.e_stop(event);
- startSearch(cm, getSearchState(cm), query);
- cm.execCommand(cmd);
- } else if (cmd == "find" || cmd == "findPersistent") {
- CodeMirror.e_stop(event);
- searchNext(query, event);
- }
- });
- if (immediate && q) {
- startSearch(cm, state, q);
- findNext(cm, rev);
- }
- } else {
- dialog(cm, queryDialog, "Search for:", q, function(query) {
- if (query && !state.query) cm.operation(function() {
- startSearch(cm, state, query);
- state.posFrom = state.posTo = cm.getCursor();
- findNext(cm, rev);
- });
- });
- }
- }
-
- function findNext(cm, rev, callback) {cm.operation(function() {
- var state = getSearchState(cm);
- var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
- if (!cursor.find(rev)) {
- cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
- if (!cursor.find(rev)) return;
- }
- cm.setSelection(cursor.from(), cursor.to());
- cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
- state.posFrom = cursor.from(); state.posTo = cursor.to();
- if (callback) callback(cursor.from(), cursor.to())
- });}
-
- function clearSearch(cm) {cm.operation(function() {
- var state = getSearchState(cm);
- state.lastQuery = state.query;
- if (!state.query) return;
- state.query = state.queryText = null;
- cm.removeOverlay(state.overlay);
- if (state.annotate) { state.annotate.clear(); state.annotate = null; }
- });}
-
- var replaceQueryDialog =
- ' (Use /re/ syntax for regexp search) ';
- var replacementQueryDialog = 'With: ';
- var doReplaceConfirm = 'Replace? Yes No All Stop ';
-
- function replaceAll(cm, query, text) {
- cm.operation(function() {
- for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
- if (typeof query != "string") {
- var match = cm.getRange(cursor.from(), cursor.to()).match(query);
- cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
- } else cursor.replace(text);
- }
- });
- }
-
- function replace(cm, all) {
- if (cm.getOption("readOnly")) return;
- var query = cm.getSelection() || getSearchState(cm).lastQuery;
- var dialogText = '' + (all ? 'Replace all:' : 'Replace:') + ' ';
- dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
- if (!query) return;
- query = parseQuery(query);
- dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
- text = parseString(text)
- if (all) {
- replaceAll(cm, query, text)
- } else {
- clearSearch(cm);
- var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
- var advance = function() {
- var start = cursor.from(), match;
- if (!(match = cursor.findNext())) {
- cursor = getSearchCursor(cm, query);
- if (!(match = cursor.findNext()) ||
- (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
- }
- cm.setSelection(cursor.from(), cursor.to());
- cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
- confirmDialog(cm, doReplaceConfirm, "Replace?",
- [function() {doReplace(match);}, advance,
- function() {replaceAll(cm, query, text)}]);
- };
- var doReplace = function(match) {
- cursor.replace(typeof query == "string" ? text :
- text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
- advance();
- };
- advance();
- }
- });
- });
- }
-
- CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
- CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
- CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
- CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
- CodeMirror.commands.findNext = doSearch;
- CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
- CodeMirror.commands.clearSearch = clearSearch;
- CodeMirror.commands.replace = replace;
- CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
-});
diff --git a/js/DevHelper/Lib/CodeMirror/addon/search/searchcursor.js b/js/DevHelper/Lib/CodeMirror/addon/search/searchcursor.js
deleted file mode 100644
index eccd81a..0000000
--- a/js/DevHelper/Lib/CodeMirror/addon/search/searchcursor.js
+++ /dev/null
@@ -1,289 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"))
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod)
- else // Plain browser env
- mod(CodeMirror)
-})(function(CodeMirror) {
- "use strict"
- var Pos = CodeMirror.Pos
-
- function regexpFlags(regexp) {
- var flags = regexp.flags
- return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
- + (regexp.global ? "g" : "")
- + (regexp.multiline ? "m" : "")
- }
-
- function ensureGlobal(regexp) {
- return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
- }
-
- function maybeMultiline(regexp) {
- return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
- }
-
- function searchRegexpForward(doc, regexp, start) {
- regexp = ensureGlobal(regexp)
- for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
- regexp.lastIndex = ch
- var string = doc.getLine(line), match = regexp.exec(string)
- if (match)
- return {from: Pos(line, match.index),
- to: Pos(line, match.index + match[0].length),
- match: match}
- }
- }
-
- function searchRegexpForwardMultiline(doc, regexp, start) {
- if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
-
- regexp = ensureGlobal(regexp)
- var string, chunk = 1
- for (var line = start.line, last = doc.lastLine(); line <= last;) {
- // This grows the search buffer in exponentially-sized chunks
- // between matches, so that nearby matches are fast and don't
- // require concatenating the whole document (in case we're
- // searching for something that has tons of matches), but at the
- // same time, the amount of retries is limited.
- for (var i = 0; i < chunk; i++) {
- var curLine = doc.getLine(line++)
- string = string == null ? curLine : string + "\n" + curLine
- }
- chunk = chunk * 2
- regexp.lastIndex = start.ch
- var match = regexp.exec(string)
- if (match) {
- var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
- var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
- return {from: Pos(startLine, startCh),
- to: Pos(startLine + inside.length - 1,
- inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
- match: match}
- }
- }
- }
-
- function lastMatchIn(string, regexp) {
- var cutOff = 0, match
- for (;;) {
- regexp.lastIndex = cutOff
- var newMatch = regexp.exec(string)
- if (!newMatch) return match
- match = newMatch
- cutOff = match.index + (match[0].length || 1)
- if (cutOff == string.length) return match
- }
- }
-
- function searchRegexpBackward(doc, regexp, start) {
- regexp = ensureGlobal(regexp)
- for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
- var string = doc.getLine(line)
- if (ch > -1) string = string.slice(0, ch)
- var match = lastMatchIn(string, regexp)
- if (match)
- return {from: Pos(line, match.index),
- to: Pos(line, match.index + match[0].length),
- match: match}
- }
- }
-
- function searchRegexpBackwardMultiline(doc, regexp, start) {
- regexp = ensureGlobal(regexp)
- var string, chunk = 1
- for (var line = start.line, first = doc.firstLine(); line >= first;) {
- for (var i = 0; i < chunk; i++) {
- var curLine = doc.getLine(line--)
- string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
- }
- chunk *= 2
-
- var match = lastMatchIn(string, regexp)
- if (match) {
- var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
- var startLine = line + before.length, startCh = before[before.length - 1].length
- return {from: Pos(startLine, startCh),
- to: Pos(startLine + inside.length - 1,
- inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
- match: match}
- }
- }
- }
-
- var doFold, noFold
- if (String.prototype.normalize) {
- doFold = function(str) { return str.normalize("NFD").toLowerCase() }
- noFold = function(str) { return str.normalize("NFD") }
- } else {
- doFold = function(str) { return str.toLowerCase() }
- noFold = function(str) { return str }
- }
-
- // Maps a position in a case-folded line back to a position in the original line
- // (compensating for codepoints increasing in number during folding)
- function adjustPos(orig, folded, pos, foldFunc) {
- if (orig.length == folded.length) return pos
- for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
- if (min == max) return min
- var mid = (min + max) >> 1
- var len = foldFunc(orig.slice(0, mid)).length
- if (len == pos) return mid
- else if (len > pos) max = mid
- else min = mid + 1
- }
- }
-
- function searchStringForward(doc, query, start, caseFold) {
- // Empty string would match anything and never progress, so we
- // define it to match nothing instead.
- if (!query.length) return null
- var fold = caseFold ? doFold : noFold
- var lines = fold(query).split(/\r|\n\r?/)
-
- search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
- var orig = doc.getLine(line).slice(ch), string = fold(orig)
- if (lines.length == 1) {
- var found = string.indexOf(lines[0])
- if (found == -1) continue search
- var start = adjustPos(orig, string, found, fold) + ch
- return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
- to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
- } else {
- var cutFrom = string.length - lines[0].length
- if (string.slice(cutFrom) != lines[0]) continue search
- for (var i = 1; i < lines.length - 1; i++)
- if (fold(doc.getLine(line + i)) != lines[i]) continue search
- var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
- if (end.slice(0, lastLine.length) != lastLine) continue search
- return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
- to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
- }
- }
- }
-
- function searchStringBackward(doc, query, start, caseFold) {
- if (!query.length) return null
- var fold = caseFold ? doFold : noFold
- var lines = fold(query).split(/\r|\n\r?/)
-
- search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
- var orig = doc.getLine(line)
- if (ch > -1) orig = orig.slice(0, ch)
- var string = fold(orig)
- if (lines.length == 1) {
- var found = string.lastIndexOf(lines[0])
- if (found == -1) continue search
- return {from: Pos(line, adjustPos(orig, string, found, fold)),
- to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
- } else {
- var lastLine = lines[lines.length - 1]
- if (string.slice(0, lastLine.length) != lastLine) continue search
- for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
- if (fold(doc.getLine(start + i)) != lines[i]) continue search
- var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
- if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
- return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
- to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
- }
- }
- }
-
- function SearchCursor(doc, query, pos, options) {
- this.atOccurrence = false
- this.doc = doc
- pos = pos ? doc.clipPos(pos) : Pos(0, 0)
- this.pos = {from: pos, to: pos}
-
- var caseFold
- if (typeof options == "object") {
- caseFold = options.caseFold
- } else { // Backwards compat for when caseFold was the 4th argument
- caseFold = options
- options = null
- }
-
- if (typeof query == "string") {
- if (caseFold == null) caseFold = false
- this.matches = function(reverse, pos) {
- return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
- }
- } else {
- query = ensureGlobal(query)
- if (!options || options.multiline !== false)
- this.matches = function(reverse, pos) {
- return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
- }
- else
- this.matches = function(reverse, pos) {
- return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
- }
- }
- }
-
- SearchCursor.prototype = {
- findNext: function() {return this.find(false)},
- findPrevious: function() {return this.find(true)},
-
- find: function(reverse) {
- var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
-
- // Implements weird auto-growing behavior on null-matches for
- // backwards-compatiblity with the vim code (unfortunately)
- while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
- if (reverse) {
- if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
- else if (result.from.line == this.doc.firstLine()) result = null
- else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
- } else {
- if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
- else if (result.to.line == this.doc.lastLine()) result = null
- else result = this.matches(reverse, Pos(result.to.line + 1, 0))
- }
- }
-
- if (result) {
- this.pos = result
- this.atOccurrence = true
- return this.pos.match || true
- } else {
- var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
- this.pos = {from: end, to: end}
- return this.atOccurrence = false
- }
- },
-
- from: function() {if (this.atOccurrence) return this.pos.from},
- to: function() {if (this.atOccurrence) return this.pos.to},
-
- replace: function(newText, origin) {
- if (!this.atOccurrence) return
- var lines = CodeMirror.splitLines(newText)
- this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
- this.pos.to = Pos(this.pos.from.line + lines.length - 1,
- lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
- }
- }
-
- CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
- return new SearchCursor(this.doc, query, pos, caseFold)
- })
- CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
- return new SearchCursor(this, query, pos, caseFold)
- })
-
- CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
- var ranges = []
- var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
- while (cur.findNext()) {
- if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
- ranges.push({anchor: cur.from(), head: cur.to()})
- }
- if (ranges.length)
- this.setSelections(ranges, 0)
- })
-});
diff --git a/js/DevHelper/Lib/CodeMirror/lib/codemirror.css b/js/DevHelper/Lib/CodeMirror/lib/codemirror.css
deleted file mode 100644
index b008351..0000000
--- a/js/DevHelper/Lib/CodeMirror/lib/codemirror.css
+++ /dev/null
@@ -1,340 +0,0 @@
-/* BASICS */
-
-.CodeMirror {
- /* Set height, width, borders, and global font properties here */
- font-family: monospace;
- height: 300px;
- color: black;
-}
-
-/* PADDING */
-
-.CodeMirror-lines {
- padding: 4px 0; /* Vertical padding around content */
-}
-.CodeMirror pre {
- padding: 0 4px; /* Horizontal padding of content */
-}
-
-.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
- background-color: white; /* The little square between H and V scrollbars */
-}
-
-/* GUTTER */
-
-.CodeMirror-gutters {
- border-right: 1px solid #ddd;
- background-color: #f7f7f7;
- white-space: nowrap;
-}
-.CodeMirror-linenumbers {}
-.CodeMirror-linenumber {
- padding: 0 3px 0 5px;
- min-width: 20px;
- text-align: right;
- color: #999;
- white-space: nowrap;
-}
-
-.CodeMirror-guttermarker { color: black; }
-.CodeMirror-guttermarker-subtle { color: #999; }
-
-/* CURSOR */
-
-.CodeMirror-cursor {
- border-left: 1px solid black;
- border-right: none;
- width: 0;
-}
-/* Shown when moving in bi-directional text */
-.CodeMirror div.CodeMirror-secondarycursor {
- border-left: 1px solid silver;
-}
-.cm-fat-cursor .CodeMirror-cursor {
- width: auto;
- border: 0 !important;
- background: #7e7;
-}
-.cm-fat-cursor div.CodeMirror-cursors {
- z-index: 1;
-}
-
-.cm-animate-fat-cursor {
- width: auto;
- border: 0;
- -webkit-animation: blink 1.06s steps(1) infinite;
- -moz-animation: blink 1.06s steps(1) infinite;
- animation: blink 1.06s steps(1) infinite;
- background-color: #7e7;
-}
-@-moz-keyframes blink {
- 0% {}
- 50% { background-color: transparent; }
- 100% {}
-}
-@-webkit-keyframes blink {
- 0% {}
- 50% { background-color: transparent; }
- 100% {}
-}
-@keyframes blink {
- 0% {}
- 50% { background-color: transparent; }
- 100% {}
-}
-
-/* Can style cursor different in overwrite (non-insert) mode */
-.CodeMirror-overwrite .CodeMirror-cursor {}
-
-.cm-tab { display: inline-block; text-decoration: inherit; }
-
-.CodeMirror-rulers {
- position: absolute;
- left: 0; right: 0; top: -50px; bottom: -20px;
- overflow: hidden;
-}
-.CodeMirror-ruler {
- border-left: 1px solid #ccc;
- top: 0; bottom: 0;
- position: absolute;
-}
-
-/* DEFAULT THEME */
-
-.cm-s-default .cm-header {color: blue;}
-.cm-s-default .cm-quote {color: #090;}
-.cm-negative {color: #d44;}
-.cm-positive {color: #292;}
-.cm-header, .cm-strong {font-weight: bold;}
-.cm-em {font-style: italic;}
-.cm-link {text-decoration: underline;}
-.cm-strikethrough {text-decoration: line-through;}
-
-.cm-s-default .cm-keyword {color: #708;}
-.cm-s-default .cm-atom {color: #219;}
-.cm-s-default .cm-number {color: #164;}
-.cm-s-default .cm-def {color: #00f;}
-.cm-s-default .cm-variable,
-.cm-s-default .cm-punctuation,
-.cm-s-default .cm-property,
-.cm-s-default .cm-operator {}
-.cm-s-default .cm-variable-2 {color: #05a;}
-.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
-.cm-s-default .cm-comment {color: #a50;}
-.cm-s-default .cm-string {color: #a11;}
-.cm-s-default .cm-string-2 {color: #f50;}
-.cm-s-default .cm-meta {color: #555;}
-.cm-s-default .cm-qualifier {color: #555;}
-.cm-s-default .cm-builtin {color: #30a;}
-.cm-s-default .cm-bracket {color: #997;}
-.cm-s-default .cm-tag {color: #170;}
-.cm-s-default .cm-attribute {color: #00c;}
-.cm-s-default .cm-hr {color: #999;}
-.cm-s-default .cm-link {color: #00c;}
-
-.cm-s-default .cm-error {color: #f00;}
-.cm-invalidchar {color: #f00;}
-
-.CodeMirror-composing { border-bottom: 2px solid; }
-
-/* Default styles for common addons */
-
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
-.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
-.CodeMirror-activeline-background {background: #e8f2ff;}
-
-/* STOP */
-
-/* The rest of this file contains styles related to the mechanics of
- the editor. You probably shouldn't touch them. */
-
-.CodeMirror {
- position: relative;
- overflow: hidden;
- background: white;
-}
-
-.CodeMirror-scroll {
- overflow: scroll !important; /* Things will break if this is overridden */
- /* 30px is the magic margin used to hide the element's real scrollbars */
- /* See overflow: hidden in .CodeMirror */
- margin-bottom: -30px; margin-right: -30px;
- padding-bottom: 30px;
- height: 100%;
- outline: none; /* Prevent dragging from highlighting the element */
- position: relative;
-}
-.CodeMirror-sizer {
- position: relative;
- border-right: 30px solid transparent;
-}
-
-/* The fake, visible scrollbars. Used to force redraw during scrolling
- before actual scrolling happens, thus preventing shaking and
- flickering artifacts. */
-.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
- position: absolute;
- z-index: 6;
- display: none;
-}
-.CodeMirror-vscrollbar {
- right: 0; top: 0;
- overflow-x: hidden;
- overflow-y: scroll;
-}
-.CodeMirror-hscrollbar {
- bottom: 0; left: 0;
- overflow-y: hidden;
- overflow-x: scroll;
-}
-.CodeMirror-scrollbar-filler {
- right: 0; bottom: 0;
-}
-.CodeMirror-gutter-filler {
- left: 0; bottom: 0;
-}
-
-.CodeMirror-gutters {
- position: absolute; left: 0; top: 0;
- min-height: 100%;
- z-index: 3;
-}
-.CodeMirror-gutter {
- white-space: normal;
- height: 100%;
- display: inline-block;
- vertical-align: top;
- margin-bottom: -30px;
-}
-.CodeMirror-gutter-wrapper {
- position: absolute;
- z-index: 4;
- background: none !important;
- border: none !important;
-}
-.CodeMirror-gutter-background {
- position: absolute;
- top: 0; bottom: 0;
- z-index: 4;
-}
-.CodeMirror-gutter-elt {
- position: absolute;
- cursor: default;
- z-index: 4;
-}
-.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
-.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
-
-.CodeMirror-lines {
- cursor: text;
- min-height: 1px; /* prevents collapsing before first draw */
-}
-.CodeMirror pre {
- /* Reset some styles that the rest of the page might have set */
- -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
- border-width: 0;
- background: transparent;
- font-family: inherit;
- font-size: inherit;
- margin: 0;
- white-space: pre;
- word-wrap: normal;
- line-height: inherit;
- color: inherit;
- z-index: 2;
- position: relative;
- overflow: visible;
- -webkit-tap-highlight-color: transparent;
- -webkit-font-variant-ligatures: contextual;
- font-variant-ligatures: contextual;
-}
-.CodeMirror-wrap pre {
- word-wrap: break-word;
- white-space: pre-wrap;
- word-break: normal;
-}
-
-.CodeMirror-linebackground {
- position: absolute;
- left: 0; right: 0; top: 0; bottom: 0;
- z-index: 0;
-}
-
-.CodeMirror-linewidget {
- position: relative;
- z-index: 2;
- overflow: auto;
-}
-
-.CodeMirror-widget {}
-
-.CodeMirror-rtl pre { direction: rtl; }
-
-.CodeMirror-code {
- outline: none;
-}
-
-/* Force content-box sizing for the elements where we expect it */
-.CodeMirror-scroll,
-.CodeMirror-sizer,
-.CodeMirror-gutter,
-.CodeMirror-gutters,
-.CodeMirror-linenumber {
- -moz-box-sizing: content-box;
- box-sizing: content-box;
-}
-
-.CodeMirror-measure {
- position: absolute;
- width: 100%;
- height: 0;
- overflow: hidden;
- visibility: hidden;
-}
-
-.CodeMirror-cursor {
- position: absolute;
- pointer-events: none;
-}
-.CodeMirror-measure pre { position: static; }
-
-div.CodeMirror-cursors {
- visibility: hidden;
- position: relative;
- z-index: 3;
-}
-div.CodeMirror-dragcursors {
- visibility: visible;
-}
-
-.CodeMirror-focused div.CodeMirror-cursors {
- visibility: visible;
-}
-
-.CodeMirror-selected { background: #d9d9d9; }
-.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
-.CodeMirror-crosshair { cursor: crosshair; }
-.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
-.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
-
-.cm-searching {
- background: #ffa;
- background: rgba(255, 255, 0, .4);
-}
-
-/* Used to force a border model for a node */
-.cm-force-border { padding-right: .1px; }
-
-@media print {
- /* Hide the cursor when printing */
- .CodeMirror div.CodeMirror-cursors {
- visibility: hidden;
- }
-}
-
-/* See issue #2901 */
-.cm-tab-wrap-hack:after { content: ''; }
-
-/* Help users use markselection to safely style text background */
-span.CodeMirror-selectedtext { background: none; }
diff --git a/js/DevHelper/Lib/CodeMirror/lib/codemirror.js b/js/DevHelper/Lib/CodeMirror/lib/codemirror.js
deleted file mode 100644
index aba145d..0000000
--- a/js/DevHelper/Lib/CodeMirror/lib/codemirror.js
+++ /dev/null
@@ -1,9479 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// This is CodeMirror (http://codemirror.net), a code editor
-// implemented in JavaScript on top of the browser's DOM.
-//
-// You can find some technical background for some of the code below
-// at http://marijnhaverbeke.nl/blog/#cm-internals .
-
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.CodeMirror = factory());
-}(this, (function () { 'use strict';
-
-// Kludges for bugs and behavior differences that can't be feature
-// detected are enabled based on userAgent etc sniffing.
-var userAgent = navigator.userAgent
-var platform = navigator.platform
-
-var gecko = /gecko\/\d/i.test(userAgent)
-var ie_upto10 = /MSIE \d/.test(userAgent)
-var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent)
-var edge = /Edge\/(\d+)/.exec(userAgent)
-var ie = ie_upto10 || ie_11up || edge
-var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1])
-var webkit = !edge && /WebKit\//.test(userAgent)
-var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent)
-var chrome = !edge && /Chrome\//.test(userAgent)
-var presto = /Opera\//.test(userAgent)
-var safari = /Apple Computer/.test(navigator.vendor)
-var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent)
-var phantom = /PhantomJS/.test(userAgent)
-
-var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent)
-var android = /Android/.test(userAgent)
-// This is woefully incomplete. Suggestions for alternative methods welcome.
-var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent)
-var mac = ios || /Mac/.test(platform)
-var chromeOS = /\bCrOS\b/.test(userAgent)
-var windows = /win/i.test(platform)
-
-var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/)
-if (presto_version) { presto_version = Number(presto_version[1]) }
-if (presto_version && presto_version >= 15) { presto = false; webkit = true }
-// Some browsers use the wrong event properties to signal cmd/ctrl on OS X
-var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11))
-var captureRightClick = gecko || (ie && ie_version >= 9)
-
-function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
-
-var rmClass = function(node, cls) {
- var current = node.className
- var match = classTest(cls).exec(current)
- if (match) {
- var after = current.slice(match.index + match[0].length)
- node.className = current.slice(0, match.index) + (after ? match[1] + after : "")
- }
-}
-
-function removeChildren(e) {
- for (var count = e.childNodes.length; count > 0; --count)
- { e.removeChild(e.firstChild) }
- return e
-}
-
-function removeChildrenAndAdd(parent, e) {
- return removeChildren(parent).appendChild(e)
-}
-
-function elt(tag, content, className, style) {
- var e = document.createElement(tag)
- if (className) { e.className = className }
- if (style) { e.style.cssText = style }
- if (typeof content == "string") { e.appendChild(document.createTextNode(content)) }
- else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]) } }
- return e
-}
-// wrapper for elt, which removes the elt from the accessibility tree
-function eltP(tag, content, className, style) {
- var e = elt(tag, content, className, style)
- e.setAttribute("role", "presentation")
- return e
-}
-
-var range
-if (document.createRange) { range = function(node, start, end, endNode) {
- var r = document.createRange()
- r.setEnd(endNode || node, end)
- r.setStart(node, start)
- return r
-} }
-else { range = function(node, start, end) {
- var r = document.body.createTextRange()
- try { r.moveToElementText(node.parentNode) }
- catch(e) { return r }
- r.collapse(true)
- r.moveEnd("character", end)
- r.moveStart("character", start)
- return r
-} }
-
-function contains(parent, child) {
- if (child.nodeType == 3) // Android browser always returns false when child is a textnode
- { child = child.parentNode }
- if (parent.contains)
- { return parent.contains(child) }
- do {
- if (child.nodeType == 11) { child = child.host }
- if (child == parent) { return true }
- } while (child = child.parentNode)
-}
-
-function activeElt() {
- // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
- // IE < 10 will throw when accessed while the page is loading or in an iframe.
- // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
- var activeElement
- try {
- activeElement = document.activeElement
- } catch(e) {
- activeElement = document.body || null
- }
- while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)
- { activeElement = activeElement.shadowRoot.activeElement }
- return activeElement
-}
-
-function addClass(node, cls) {
- var current = node.className
- if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls }
-}
-function joinClasses(a, b) {
- var as = a.split(" ")
- for (var i = 0; i < as.length; i++)
- { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } }
- return b
-}
-
-var selectInput = function(node) { node.select() }
-if (ios) // Mobile Safari apparently has a bug where select() is broken.
- { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } }
-else if (ie) // Suppress mysterious IE10 errors
- { selectInput = function(node) { try { node.select() } catch(_e) {} } }
-
-function bind(f) {
- var args = Array.prototype.slice.call(arguments, 1)
- return function(){return f.apply(null, args)}
-}
-
-function copyObj(obj, target, overwrite) {
- if (!target) { target = {} }
- for (var prop in obj)
- { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
- { target[prop] = obj[prop] } }
- return target
-}
-
-// Counts the column offset in a string, taking tabs into account.
-// Used mostly to find indentation.
-function countColumn(string, end, tabSize, startIndex, startValue) {
- if (end == null) {
- end = string.search(/[^\s\u00a0]/)
- if (end == -1) { end = string.length }
- }
- for (var i = startIndex || 0, n = startValue || 0;;) {
- var nextTab = string.indexOf("\t", i)
- if (nextTab < 0 || nextTab >= end)
- { return n + (end - i) }
- n += nextTab - i
- n += tabSize - (n % tabSize)
- i = nextTab + 1
- }
-}
-
-var Delayed = function() {this.id = null};
-Delayed.prototype.set = function (ms, f) {
- clearTimeout(this.id)
- this.id = setTimeout(f, ms)
-};
-
-function indexOf(array, elt) {
- for (var i = 0; i < array.length; ++i)
- { if (array[i] == elt) { return i } }
- return -1
-}
-
-// Number of pixels added to scroller and sizer to hide scrollbar
-var scrollerGap = 30
-
-// Returned or thrown by various protocols to signal 'I'm not
-// handling this'.
-var Pass = {toString: function(){return "CodeMirror.Pass"}}
-
-// Reused option objects for setSelection & friends
-var sel_dontScroll = {scroll: false};
-var sel_mouse = {origin: "*mouse"};
-var sel_move = {origin: "+move"};
-// The inverse of countColumn -- find the offset that corresponds to
-// a particular column.
-function findColumn(string, goal, tabSize) {
- for (var pos = 0, col = 0;;) {
- var nextTab = string.indexOf("\t", pos)
- if (nextTab == -1) { nextTab = string.length }
- var skipped = nextTab - pos
- if (nextTab == string.length || col + skipped >= goal)
- { return pos + Math.min(skipped, goal - col) }
- col += nextTab - pos
- col += tabSize - (col % tabSize)
- pos = nextTab + 1
- if (col >= goal) { return pos }
- }
-}
-
-var spaceStrs = [""]
-function spaceStr(n) {
- while (spaceStrs.length <= n)
- { spaceStrs.push(lst(spaceStrs) + " ") }
- return spaceStrs[n]
-}
-
-function lst(arr) { return arr[arr.length-1] }
-
-function map(array, f) {
- var out = []
- for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) }
- return out
-}
-
-function insertSorted(array, value, score) {
- var pos = 0, priority = score(value)
- while (pos < array.length && score(array[pos]) <= priority) { pos++ }
- array.splice(pos, 0, value)
-}
-
-function nothing() {}
-
-function createObj(base, props) {
- var inst
- if (Object.create) {
- inst = Object.create(base)
- } else {
- nothing.prototype = base
- inst = new nothing()
- }
- if (props) { copyObj(props, inst) }
- return inst
-}
-
-var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/
-function isWordCharBasic(ch) {
- return /\w/.test(ch) || ch > "\x80" &&
- (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))
-}
-function isWordChar(ch, helper) {
- if (!helper) { return isWordCharBasic(ch) }
- if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true }
- return helper.test(ch)
-}
-
-function isEmpty(obj) {
- for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }
- return true
-}
-
-// Extending unicode characters. A series of a non-extending char +
-// any number of extending chars is treated as a single unit as far
-// as editing and measuring is concerned. This is not fully correct,
-// since some scripts/fonts/browsers also treat other configurations
-// of code points as a group.
-var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/
-function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }
-
-// Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.
-function skipExtendingChars(str, pos, dir) {
- while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir }
- return pos
-}
-
-// Returns the value from the range [`from`; `to`] that satisfies
-// `pred` and is closest to `from`. Assumes that at least `to` satisfies `pred`.
-function findFirst(pred, from, to) {
- for (;;) {
- if (Math.abs(from - to) <= 1) { return pred(from) ? from : to }
- var mid = Math.floor((from + to) / 2)
- if (pred(mid)) { to = mid }
- else { from = mid }
- }
-}
-
-// The display handles the DOM integration, both for input reading
-// and content drawing. It holds references to DOM nodes and
-// display-related state.
-
-function Display(place, doc, input) {
- var d = this
- this.input = input
-
- // Covers bottom-right square when both scrollbars are present.
- d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler")
- d.scrollbarFiller.setAttribute("cm-not-content", "true")
- // Covers bottom of gutter when coverGutterNextToScrollbar is on
- // and h scrollbar is present.
- d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler")
- d.gutterFiller.setAttribute("cm-not-content", "true")
- // Will contain the actual code, positioned to cover the viewport.
- d.lineDiv = eltP("div", null, "CodeMirror-code")
- // Elements are added to these to represent selection and cursors.
- d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1")
- d.cursorDiv = elt("div", null, "CodeMirror-cursors")
- // A visibility: hidden element used to find the size of things.
- d.measure = elt("div", null, "CodeMirror-measure")
- // When lines outside of the viewport are measured, they are drawn in this.
- d.lineMeasure = elt("div", null, "CodeMirror-measure")
- // Wraps everything that needs to exist inside the vertically-padded coordinate system
- d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
- null, "position: relative; outline: none")
- var lines = eltP("div", [d.lineSpace], "CodeMirror-lines")
- // Moved around its parent to cover visible view.
- d.mover = elt("div", [lines], null, "position: relative")
- // Set to the height of the document, allowing scrolling.
- d.sizer = elt("div", [d.mover], "CodeMirror-sizer")
- d.sizerWidth = null
- // Behavior of elts with overflow: auto and padding is
- // inconsistent across browsers. This is used to ensure the
- // scrollable area is big enough.
- d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;")
- // Will contain the gutters, if any.
- d.gutters = elt("div", null, "CodeMirror-gutters")
- d.lineGutter = null
- // Actual scrollable element.
- d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll")
- d.scroller.setAttribute("tabIndex", "-1")
- // The element in which the editor lives.
- d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror")
-
- // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
- if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 }
- if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true }
-
- if (place) {
- if (place.appendChild) { place.appendChild(d.wrapper) }
- else { place(d.wrapper) }
- }
-
- // Current rendered range (may be bigger than the view window).
- d.viewFrom = d.viewTo = doc.first
- d.reportedViewFrom = d.reportedViewTo = doc.first
- // Information about the rendered lines.
- d.view = []
- d.renderedView = null
- // Holds info about a single rendered line when it was rendered
- // for measurement, while not in view.
- d.externalMeasured = null
- // Empty space (in pixels) above the view
- d.viewOffset = 0
- d.lastWrapHeight = d.lastWrapWidth = 0
- d.updateLineNumbers = null
-
- d.nativeBarWidth = d.barHeight = d.barWidth = 0
- d.scrollbarsClipped = false
-
- // Used to only resize the line number gutter when necessary (when
- // the amount of lines crosses a boundary that makes its width change)
- d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null
- // Set to true when a non-horizontal-scrolling line widget is
- // added. As an optimization, line widget aligning is skipped when
- // this is false.
- d.alignWidgets = false
-
- d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null
-
- // Tracks the maximum line length so that the horizontal scrollbar
- // can be kept static when scrolling.
- d.maxLine = null
- d.maxLineLength = 0
- d.maxLineChanged = false
-
- // Used for measuring wheel scrolling granularity
- d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null
-
- // True when shift is held down.
- d.shift = false
-
- // Used to track whether anything happened since the context menu
- // was opened.
- d.selForContextMenu = null
-
- d.activeTouch = null
-
- input.init(d)
-}
-
-// Find the line object corresponding to the given line number.
-function getLine(doc, n) {
- n -= doc.first
- if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") }
- var chunk = doc
- while (!chunk.lines) {
- for (var i = 0;; ++i) {
- var child = chunk.children[i], sz = child.chunkSize()
- if (n < sz) { chunk = child; break }
- n -= sz
- }
- }
- return chunk.lines[n]
-}
-
-// Get the part of a document between two positions, as an array of
-// strings.
-function getBetween(doc, start, end) {
- var out = [], n = start.line
- doc.iter(start.line, end.line + 1, function (line) {
- var text = line.text
- if (n == end.line) { text = text.slice(0, end.ch) }
- if (n == start.line) { text = text.slice(start.ch) }
- out.push(text)
- ++n
- })
- return out
-}
-// Get the lines between from and to, as array of strings.
-function getLines(doc, from, to) {
- var out = []
- doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts when callback returns truthy value
- return out
-}
-
-// Update the height of a line, propagating the height change
-// upwards to parent nodes.
-function updateLineHeight(line, height) {
- var diff = height - line.height
- if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } }
-}
-
-// Given a line object, find its line number by walking up through
-// its parent links.
-function lineNo(line) {
- if (line.parent == null) { return null }
- var cur = line.parent, no = indexOf(cur.lines, line)
- for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
- for (var i = 0;; ++i) {
- if (chunk.children[i] == cur) { break }
- no += chunk.children[i].chunkSize()
- }
- }
- return no + cur.first
-}
-
-// Find the line at the given vertical position, using the height
-// information in the document tree.
-function lineAtHeight(chunk, h) {
- var n = chunk.first
- outer: do {
- for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {
- var child = chunk.children[i$1], ch = child.height
- if (h < ch) { chunk = child; continue outer }
- h -= ch
- n += child.chunkSize()
- }
- return n
- } while (!chunk.lines)
- var i = 0
- for (; i < chunk.lines.length; ++i) {
- var line = chunk.lines[i], lh = line.height
- if (h < lh) { break }
- h -= lh
- }
- return n + i
-}
-
-function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}
-
-function lineNumberFor(options, i) {
- return String(options.lineNumberFormatter(i + options.firstLineNumber))
-}
-
-// A Pos instance represents a position within the text.
-function Pos(line, ch, sticky) {
- if ( sticky === void 0 ) sticky = null;
-
- if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }
- this.line = line
- this.ch = ch
- this.sticky = sticky
-}
-
-// Compare two positions, return 0 if they are the same, a negative
-// number when a is less, and a positive number otherwise.
-function cmp(a, b) { return a.line - b.line || a.ch - b.ch }
-
-function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }
-
-function copyPos(x) {return Pos(x.line, x.ch)}
-function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }
-function minPos(a, b) { return cmp(a, b) < 0 ? a : b }
-
-// Most of the external API clips given positions to make sure they
-// actually exist within the document.
-function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))}
-function clipPos(doc, pos) {
- if (pos.line < doc.first) { return Pos(doc.first, 0) }
- var last = doc.first + doc.size - 1
- if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) }
- return clipToLen(pos, getLine(doc, pos.line).text.length)
-}
-function clipToLen(pos, linelen) {
- var ch = pos.ch
- if (ch == null || ch > linelen) { return Pos(pos.line, linelen) }
- else if (ch < 0) { return Pos(pos.line, 0) }
- else { return pos }
-}
-function clipPosArray(doc, array) {
- var out = []
- for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) }
- return out
-}
-
-// Optimize some code when these features are not used.
-var sawReadOnlySpans = false;
-var sawCollapsedSpans = false;
-function seeReadOnlySpans() {
- sawReadOnlySpans = true
-}
-
-function seeCollapsedSpans() {
- sawCollapsedSpans = true
-}
-
-// TEXTMARKER SPANS
-
-function MarkedSpan(marker, from, to) {
- this.marker = marker
- this.from = from; this.to = to
-}
-
-// Search an array of spans for a span matching the given marker.
-function getMarkedSpanFor(spans, marker) {
- if (spans) { for (var i = 0; i < spans.length; ++i) {
- var span = spans[i]
- if (span.marker == marker) { return span }
- } }
-}
-// Remove a span from an array, returning undefined if no spans are
-// left (we don't store arrays for lines without spans).
-function removeMarkedSpan(spans, span) {
- var r
- for (var i = 0; i < spans.length; ++i)
- { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } }
- return r
-}
-// Add a span to a line.
-function addMarkedSpan(line, span) {
- line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]
- span.marker.attachLine(line)
-}
-
-// Used for the algorithm that adjusts markers for a change in the
-// document. These functions cut an array of spans at a given
-// character position, returning an array of remaining chunks (or
-// undefined if nothing remains).
-function markedSpansBefore(old, startCh, isInsert) {
- var nw
- if (old) { for (var i = 0; i < old.length; ++i) {
- var span = old[i], marker = span.marker
- var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh)
- if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
- var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)
- ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to))
- }
- } }
- return nw
-}
-function markedSpansAfter(old, endCh, isInsert) {
- var nw
- if (old) { for (var i = 0; i < old.length; ++i) {
- var span = old[i], marker = span.marker
- var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh)
- if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
- var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)
- ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
- span.to == null ? null : span.to - endCh))
- }
- } }
- return nw
-}
-
-// Given a change object, compute the new set of marker spans that
-// cover the line in which the change took place. Removes spans
-// entirely within the change, reconnects spans belonging to the
-// same marker that appear on both sides of the change, and cuts off
-// spans partially within the change. Returns an array of span
-// arrays with one element for each line in (after) the change.
-function stretchSpansOverChange(doc, change) {
- if (change.full) { return null }
- var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans
- var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans
- if (!oldFirst && !oldLast) { return null }
-
- var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0
- // Get the spans that 'stick out' on both sides
- var first = markedSpansBefore(oldFirst, startCh, isInsert)
- var last = markedSpansAfter(oldLast, endCh, isInsert)
-
- // Next, merge those two ends
- var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0)
- if (first) {
- // Fix up .to properties of first
- for (var i = 0; i < first.length; ++i) {
- var span = first[i]
- if (span.to == null) {
- var found = getMarkedSpanFor(last, span.marker)
- if (!found) { span.to = startCh }
- else if (sameLine) { span.to = found.to == null ? null : found.to + offset }
- }
- }
- }
- if (last) {
- // Fix up .from in last (or move them into first in case of sameLine)
- for (var i$1 = 0; i$1 < last.length; ++i$1) {
- var span$1 = last[i$1]
- if (span$1.to != null) { span$1.to += offset }
- if (span$1.from == null) {
- var found$1 = getMarkedSpanFor(first, span$1.marker)
- if (!found$1) {
- span$1.from = offset
- if (sameLine) { (first || (first = [])).push(span$1) }
- }
- } else {
- span$1.from += offset
- if (sameLine) { (first || (first = [])).push(span$1) }
- }
- }
- }
- // Make sure we didn't create any zero-length spans
- if (first) { first = clearEmptySpans(first) }
- if (last && last != first) { last = clearEmptySpans(last) }
-
- var newMarkers = [first]
- if (!sameLine) {
- // Fill gap with whole-line-spans
- var gap = change.text.length - 2, gapMarkers
- if (gap > 0 && first)
- { for (var i$2 = 0; i$2 < first.length; ++i$2)
- { if (first[i$2].to == null)
- { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)) } } }
- for (var i$3 = 0; i$3 < gap; ++i$3)
- { newMarkers.push(gapMarkers) }
- newMarkers.push(last)
- }
- return newMarkers
-}
-
-// Remove spans that are empty and don't have a clearWhenEmpty
-// option of false.
-function clearEmptySpans(spans) {
- for (var i = 0; i < spans.length; ++i) {
- var span = spans[i]
- if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
- { spans.splice(i--, 1) }
- }
- if (!spans.length) { return null }
- return spans
-}
-
-// Used to 'clip' out readOnly ranges when making a change.
-function removeReadOnlyRanges(doc, from, to) {
- var markers = null
- doc.iter(from.line, to.line + 1, function (line) {
- if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
- var mark = line.markedSpans[i].marker
- if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
- { (markers || (markers = [])).push(mark) }
- } }
- })
- if (!markers) { return null }
- var parts = [{from: from, to: to}]
- for (var i = 0; i < markers.length; ++i) {
- var mk = markers[i], m = mk.find(0)
- for (var j = 0; j < parts.length; ++j) {
- var p = parts[j]
- if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }
- var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to)
- if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
- { newParts.push({from: p.from, to: m.from}) }
- if (dto > 0 || !mk.inclusiveRight && !dto)
- { newParts.push({from: m.to, to: p.to}) }
- parts.splice.apply(parts, newParts)
- j += newParts.length - 3
- }
- }
- return parts
-}
-
-// Connect or disconnect spans from a line.
-function detachMarkedSpans(line) {
- var spans = line.markedSpans
- if (!spans) { return }
- for (var i = 0; i < spans.length; ++i)
- { spans[i].marker.detachLine(line) }
- line.markedSpans = null
-}
-function attachMarkedSpans(line, spans) {
- if (!spans) { return }
- for (var i = 0; i < spans.length; ++i)
- { spans[i].marker.attachLine(line) }
- line.markedSpans = spans
-}
-
-// Helpers used when computing which overlapping collapsed span
-// counts as the larger one.
-function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }
-function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }
-
-// Returns a number indicating which of two overlapping collapsed
-// spans is larger (and thus includes the other). Falls back to
-// comparing ids when the spans cover exactly the same range.
-function compareCollapsedMarkers(a, b) {
- var lenDiff = a.lines.length - b.lines.length
- if (lenDiff != 0) { return lenDiff }
- var aPos = a.find(), bPos = b.find()
- var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b)
- if (fromCmp) { return -fromCmp }
- var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b)
- if (toCmp) { return toCmp }
- return b.id - a.id
-}
-
-// Find out whether a line ends or starts in a collapsed span. If
-// so, return the marker for that span.
-function collapsedSpanAtSide(line, start) {
- var sps = sawCollapsedSpans && line.markedSpans, found
- if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
- sp = sps[i]
- if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
- (!found || compareCollapsedMarkers(found, sp.marker) < 0))
- { found = sp.marker }
- } }
- return found
-}
-function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }
-function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }
-
-// Test whether there exists a collapsed span that partially
-// overlaps (covers the start or end, but not both) of a new span.
-// Such overlap is not allowed.
-function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
- var line = getLine(doc, lineNo)
- var sps = sawCollapsedSpans && line.markedSpans
- if (sps) { for (var i = 0; i < sps.length; ++i) {
- var sp = sps[i]
- if (!sp.marker.collapsed) { continue }
- var found = sp.marker.find(0)
- var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker)
- var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker)
- if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }
- if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
- fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))
- { return true }
- } }
-}
-
-// A visual line is a line as drawn on the screen. Folding, for
-// example, can cause multiple logical lines to appear on the same
-// visual line. This finds the start of the visual line that the
-// given line is part of (usually that is the line itself).
-function visualLine(line) {
- var merged
- while (merged = collapsedSpanAtStart(line))
- { line = merged.find(-1, true).line }
- return line
-}
-
-function visualLineEnd(line) {
- var merged
- while (merged = collapsedSpanAtEnd(line))
- { line = merged.find(1, true).line }
- return line
-}
-
-// Returns an array of logical lines that continue the visual line
-// started by the argument, or undefined if there are no such lines.
-function visualLineContinued(line) {
- var merged, lines
- while (merged = collapsedSpanAtEnd(line)) {
- line = merged.find(1, true).line
- ;(lines || (lines = [])).push(line)
- }
- return lines
-}
-
-// Get the line number of the start of the visual line that the
-// given line number is part of.
-function visualLineNo(doc, lineN) {
- var line = getLine(doc, lineN), vis = visualLine(line)
- if (line == vis) { return lineN }
- return lineNo(vis)
-}
-
-// Get the line number of the start of the next visual line after
-// the given line.
-function visualLineEndNo(doc, lineN) {
- if (lineN > doc.lastLine()) { return lineN }
- var line = getLine(doc, lineN), merged
- if (!lineIsHidden(doc, line)) { return lineN }
- while (merged = collapsedSpanAtEnd(line))
- { line = merged.find(1, true).line }
- return lineNo(line) + 1
-}
-
-// Compute whether a line is hidden. Lines count as hidden when they
-// are part of a visual line that starts with another line, or when
-// they are entirely covered by collapsed, non-widget span.
-function lineIsHidden(doc, line) {
- var sps = sawCollapsedSpans && line.markedSpans
- if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
- sp = sps[i]
- if (!sp.marker.collapsed) { continue }
- if (sp.from == null) { return true }
- if (sp.marker.widgetNode) { continue }
- if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
- { return true }
- } }
-}
-function lineIsHiddenInner(doc, line, span) {
- if (span.to == null) {
- var end = span.marker.find(1, true)
- return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))
- }
- if (span.marker.inclusiveRight && span.to == line.text.length)
- { return true }
- for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {
- sp = line.markedSpans[i]
- if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
- (sp.to == null || sp.to != span.from) &&
- (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
- lineIsHiddenInner(doc, line, sp)) { return true }
- }
-}
-
-// Find the height above the given line.
-function heightAtLine(lineObj) {
- lineObj = visualLine(lineObj)
-
- var h = 0, chunk = lineObj.parent
- for (var i = 0; i < chunk.lines.length; ++i) {
- var line = chunk.lines[i]
- if (line == lineObj) { break }
- else { h += line.height }
- }
- for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
- for (var i$1 = 0; i$1 < p.children.length; ++i$1) {
- var cur = p.children[i$1]
- if (cur == chunk) { break }
- else { h += cur.height }
- }
- }
- return h
-}
-
-// Compute the character length of a line, taking into account
-// collapsed ranges (see markText) that might hide parts, and join
-// other lines onto it.
-function lineLength(line) {
- if (line.height == 0) { return 0 }
- var len = line.text.length, merged, cur = line
- while (merged = collapsedSpanAtStart(cur)) {
- var found = merged.find(0, true)
- cur = found.from.line
- len += found.from.ch - found.to.ch
- }
- cur = line
- while (merged = collapsedSpanAtEnd(cur)) {
- var found$1 = merged.find(0, true)
- len -= cur.text.length - found$1.from.ch
- cur = found$1.to.line
- len += cur.text.length - found$1.to.ch
- }
- return len
-}
-
-// Find the longest line in the document.
-function findMaxLine(cm) {
- var d = cm.display, doc = cm.doc
- d.maxLine = getLine(doc, doc.first)
- d.maxLineLength = lineLength(d.maxLine)
- d.maxLineChanged = true
- doc.iter(function (line) {
- var len = lineLength(line)
- if (len > d.maxLineLength) {
- d.maxLineLength = len
- d.maxLine = line
- }
- })
-}
-
-// BIDI HELPERS
-
-function iterateBidiSections(order, from, to, f) {
- if (!order) { return f(from, to, "ltr") }
- var found = false
- for (var i = 0; i < order.length; ++i) {
- var part = order[i]
- if (part.from < to && part.to > from || from == to && part.to == from) {
- f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr")
- found = true
- }
- }
- if (!found) { f(from, to, "ltr") }
-}
-
-var bidiOther = null
-function getBidiPartAt(order, ch, sticky) {
- var found
- bidiOther = null
- for (var i = 0; i < order.length; ++i) {
- var cur = order[i]
- if (cur.from < ch && cur.to > ch) { return i }
- if (cur.to == ch) {
- if (cur.from != cur.to && sticky == "before") { found = i }
- else { bidiOther = i }
- }
- if (cur.from == ch) {
- if (cur.from != cur.to && sticky != "before") { found = i }
- else { bidiOther = i }
- }
- }
- return found != null ? found : bidiOther
-}
-
-// Bidirectional ordering algorithm
-// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
-// that this (partially) implements.
-
-// One-char codes used for character types:
-// L (L): Left-to-Right
-// R (R): Right-to-Left
-// r (AL): Right-to-Left Arabic
-// 1 (EN): European Number
-// + (ES): European Number Separator
-// % (ET): European Number Terminator
-// n (AN): Arabic Number
-// , (CS): Common Number Separator
-// m (NSM): Non-Spacing Mark
-// b (BN): Boundary Neutral
-// s (B): Paragraph Separator
-// t (S): Segment Separator
-// w (WS): Whitespace
-// N (ON): Other Neutrals
-
-// Returns null if characters are ordered as they appear
-// (left-to-right), or an array of sections ({from, to, level}
-// objects) in the order in which they occur visually.
-var bidiOrdering = (function() {
- // Character types for codepoints 0 to 0xff
- var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"
- // Character types for codepoints 0x600 to 0x6f9
- var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"
- function charType(code) {
- if (code <= 0xf7) { return lowTypes.charAt(code) }
- else if (0x590 <= code && code <= 0x5f4) { return "R" }
- else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }
- else if (0x6ee <= code && code <= 0x8ac) { return "r" }
- else if (0x2000 <= code && code <= 0x200b) { return "w" }
- else if (code == 0x200c) { return "b" }
- else { return "L" }
- }
-
- var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
- var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/
-
- function BidiSpan(level, from, to) {
- this.level = level
- this.from = from; this.to = to
- }
-
- return function(str, direction) {
- var outerType = direction == "ltr" ? "L" : "R"
-
- if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false }
- var len = str.length, types = []
- for (var i = 0; i < len; ++i)
- { types.push(charType(str.charCodeAt(i))) }
-
- // 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.
- for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {
- var type = types[i$1]
- if (type == "m") { types[i$1] = prev }
- else { prev = type }
- }
-
- // 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.
- // W3. Change all ALs to R.
- for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {
- var type$1 = types[i$2]
- if (type$1 == "1" && cur == "r") { types[i$2] = "n" }
- else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "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$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {
- var type$2 = types[i$3]
- if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" }
- else if (type$2 == "," && prev$1 == types[i$3+1] &&
- (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 }
- prev$1 = type$2
- }
-
- // W5. A sequence of European terminators adjacent to European
- // numbers changes to all European numbers.
- // W6. Otherwise, separators and terminators change to Other
- // Neutral.
- for (var i$4 = 0; i$4 < len; ++i$4) {
- var type$3 = types[i$4]
- if (type$3 == ",") { types[i$4] = "N" }
- else if (type$3 == "%") {
- var end = (void 0)
- for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {}
- var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"
- for (var j = i$4; j < end; ++j) { types[j] = replace }
- i$4 = end - 1
- }
- }
-
- // 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.
- for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {
- var type$4 = types[i$5]
- if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" }
- else if (isStrong.test(type$4)) { cur$1 = type$4 }
- }
-
- // 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 act as if they were R in
- // terms of their influence on neutrals. Start-of-level-run (sor)
- // and end-of-level-run (eor) are used at level run boundaries.
- // N2. Any remaining neutrals take the embedding direction.
- for (var i$6 = 0; i$6 < len; ++i$6) {
- if (isNeutral.test(types[i$6])) {
- var end$1 = (void 0)
- for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}
- var before = (i$6 ? types[i$6-1] : outerType) == "L"
- var after = (end$1 < len ? types[end$1] : outerType) == "L"
- var replace$1 = before == after ? (before ? "L" : "R") : outerType
- for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 }
- i$6 = end$1 - 1
- }
- }
-
- // Here we depart from the documented algorithm, in order to avoid
- // building up an actual levels array. Since there are only three
- // levels (0, 1, 2) in an implementation that doesn't take
- // explicit embedding into account, we can build up the order on
- // the fly, without following the level-based algorithm.
- var order = [], m
- for (var i$7 = 0; i$7 < len;) {
- if (countsAsLeft.test(types[i$7])) {
- var start = i$7
- for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}
- order.push(new BidiSpan(0, start, i$7))
- } else {
- var pos = i$7, at = order.length
- for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {}
- for (var j$2 = pos; j$2 < i$7;) {
- if (countsAsNum.test(types[j$2])) {
- if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) }
- var nstart = j$2
- for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
- order.splice(at, 0, new BidiSpan(2, nstart, j$2))
- pos = j$2
- } else { ++j$2 }
- }
- if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) }
- }
- }
- if (order[0].level == 1 && (m = str.match(/^\s+/))) {
- order[0].from = m[0].length
- order.unshift(new BidiSpan(0, 0, m[0].length))
- }
- if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
- lst(order).to -= m[0].length
- order.push(new BidiSpan(0, len - m[0].length, len))
- }
-
- return direction == "rtl" ? order.reverse() : order
- }
-})()
-
-// Get the bidi ordering for the given line (and cache it). Returns
-// false for lines that are fully left-to-right, and an array of
-// BidiSpan objects otherwise.
-function getOrder(line, direction) {
- var order = line.order
- if (order == null) { order = line.order = bidiOrdering(line.text, direction) }
- return order
-}
-
-function moveCharLogically(line, ch, dir) {
- var target = skipExtendingChars(line.text, ch + dir, dir)
- return target < 0 || target > line.text.length ? null : target
-}
-
-function moveLogically(line, start, dir) {
- var ch = moveCharLogically(line, start.ch, dir)
- return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before")
-}
-
-function endOfLine(visually, cm, lineObj, lineNo, dir) {
- if (visually) {
- var order = getOrder(lineObj, cm.doc.direction)
- if (order) {
- var part = dir < 0 ? lst(order) : order[0]
- var moveInStorageOrder = (dir < 0) == (part.level == 1)
- var sticky = moveInStorageOrder ? "after" : "before"
- var ch
- // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
- // it could be that the last bidi part is not on the last visual line,
- // since visual lines contain content order-consecutive chunks.
- // Thus, in rtl, we are looking for the first (content-order) character
- // in the rtl chunk that is on the last line (that is, the same line
- // as the last (content-order) character).
- if (part.level > 0) {
- var prep = prepareMeasureForLine(cm, lineObj)
- ch = dir < 0 ? lineObj.text.length - 1 : 0
- var targetTop = measureCharPrepared(cm, prep, ch).top
- ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch)
- if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) }
- } else { ch = dir < 0 ? part.to : part.from }
- return new Pos(lineNo, ch, sticky)
- }
- }
- return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after")
-}
-
-function moveVisually(cm, line, start, dir) {
- var bidi = getOrder(line, cm.doc.direction)
- if (!bidi) { return moveLogically(line, start, dir) }
- if (start.ch >= line.text.length) {
- start.ch = line.text.length
- start.sticky = "before"
- } else if (start.ch <= 0) {
- start.ch = 0
- start.sticky = "after"
- }
- var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]
- if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {
- // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,
- // nothing interesting happens.
- return moveLogically(line, start, dir)
- }
-
- var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }
- var prep
- var getWrappedLineExtent = function (ch) {
- if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }
- prep = prep || prepareMeasureForLine(cm, line)
- return wrappedLineExtentChar(cm, line, prep, ch)
- }
- var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch)
-
- if (cm.doc.direction == "rtl" || part.level == 1) {
- var moveInStorageOrder = (part.level == 1) == (dir < 0)
- var ch = mv(start, moveInStorageOrder ? 1 : -1)
- if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {
- // Case 2: We move within an rtl part or in an rtl editor on the same visual line
- var sticky = moveInStorageOrder ? "before" : "after"
- return new Pos(start.line, ch, sticky)
- }
- }
-
- // Case 3: Could not move within this bidi part in this visual line, so leave
- // the current bidi part
-
- var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {
- var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder
- ? new Pos(start.line, mv(ch, 1), "before")
- : new Pos(start.line, ch, "after"); }
-
- for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {
- var part = bidi[partPos]
- var moveInStorageOrder = (dir > 0) == (part.level != 1)
- var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1)
- if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }
- ch = moveInStorageOrder ? part.from : mv(part.to, -1)
- if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }
- }
- }
-
- // Case 3a: Look for other bidi parts on the same visual line
- var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent)
- if (res) { return res }
-
- // Case 3b: Look for other bidi parts on the next visual line
- var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1)
- if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {
- res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh))
- if (res) { return res }
- }
-
- // Case 4: Nowhere to move
- return null
-}
-
-// EVENT HANDLING
-
-// Lightweight event framework. on/off also work on DOM nodes,
-// registering native DOM handlers.
-
-var noHandlers = []
-
-var on = function(emitter, type, f) {
- if (emitter.addEventListener) {
- emitter.addEventListener(type, f, false)
- } else if (emitter.attachEvent) {
- emitter.attachEvent("on" + type, f)
- } else {
- var map = emitter._handlers || (emitter._handlers = {})
- map[type] = (map[type] || noHandlers).concat(f)
- }
-}
-
-function getHandlers(emitter, type) {
- return emitter._handlers && emitter._handlers[type] || noHandlers
-}
-
-function off(emitter, type, f) {
- if (emitter.removeEventListener) {
- emitter.removeEventListener(type, f, false)
- } else if (emitter.detachEvent) {
- emitter.detachEvent("on" + type, f)
- } else {
- var map = emitter._handlers, arr = map && map[type]
- if (arr) {
- var index = indexOf(arr, f)
- if (index > -1)
- { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) }
- }
- }
-}
-
-function signal(emitter, type /*, values...*/) {
- var handlers = getHandlers(emitter, type)
- if (!handlers.length) { return }
- var args = Array.prototype.slice.call(arguments, 2)
- for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) }
-}
-
-// The DOM events that CodeMirror handles can be overridden by
-// registering a (non-DOM) handler on the editor for the event name,
-// and preventDefault-ing the event in that handler.
-function signalDOMEvent(cm, e, override) {
- if (typeof e == "string")
- { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} }
- signal(cm, override || e.type, cm, e)
- return e_defaultPrevented(e) || e.codemirrorIgnore
-}
-
-function signalCursorActivity(cm) {
- var arr = cm._handlers && cm._handlers.cursorActivity
- if (!arr) { return }
- var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = [])
- for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)
- { set.push(arr[i]) } }
-}
-
-function hasHandler(emitter, type) {
- return getHandlers(emitter, type).length > 0
-}
-
-// Add on and off methods to a constructor's prototype, to make
-// registering events on such objects more convenient.
-function eventMixin(ctor) {
- ctor.prototype.on = function(type, f) {on(this, type, f)}
- ctor.prototype.off = function(type, f) {off(this, type, f)}
-}
-
-// Due to the fact that we still support jurassic IE versions, some
-// compatibility wrappers are needed.
-
-function e_preventDefault(e) {
- if (e.preventDefault) { e.preventDefault() }
- else { e.returnValue = false }
-}
-function e_stopPropagation(e) {
- if (e.stopPropagation) { e.stopPropagation() }
- else { e.cancelBubble = true }
-}
-function e_defaultPrevented(e) {
- return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false
-}
-function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)}
-
-function e_target(e) {return e.target || e.srcElement}
-function e_button(e) {
- var b = e.which
- if (b == null) {
- if (e.button & 1) { b = 1 }
- else if (e.button & 2) { b = 3 }
- else if (e.button & 4) { b = 2 }
- }
- if (mac && e.ctrlKey && b == 1) { b = 3 }
- return b
-}
-
-// Detect drag-and-drop
-var dragAndDrop = function() {
- // There is *some* kind of drag-and-drop support in IE6-8, but I
- // couldn't get it to work yet.
- if (ie && ie_version < 9) { return false }
- var div = elt('div')
- return "draggable" in div || "dragDrop" in div
-}()
-
-var zwspSupported
-function zeroWidthElement(measure) {
- if (zwspSupported == null) {
- var test = elt("span", "\u200b")
- removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]))
- if (measure.firstChild.offsetHeight != 0)
- { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) }
- }
- var node = zwspSupported ? elt("span", "\u200b") :
- elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px")
- node.setAttribute("cm-text", "")
- return node
-}
-
-// Feature-detect IE's crummy client rect reporting for bidi text
-var badBidiRects
-function hasBadBidiRects(measure) {
- if (badBidiRects != null) { return badBidiRects }
- var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"))
- var r0 = range(txt, 0, 1).getBoundingClientRect()
- var r1 = range(txt, 1, 2).getBoundingClientRect()
- removeChildren(measure)
- if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)
- return badBidiRects = (r1.right - r0.right < 3)
-}
-
-// See if "".split is the broken IE version, if so, provide an
-// alternative way to split lines.
-var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) {
- var pos = 0, result = [], l = string.length
- while (pos <= l) {
- var nl = string.indexOf("\n", pos)
- if (nl == -1) { nl = string.length }
- var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl)
- var rt = line.indexOf("\r")
- if (rt != -1) {
- result.push(line.slice(0, rt))
- pos += rt + 1
- } else {
- result.push(line)
- pos = nl + 1
- }
- }
- return result
-} : function (string) { return string.split(/\r\n?|\n/); }
-
-var hasSelection = window.getSelection ? function (te) {
- try { return te.selectionStart != te.selectionEnd }
- catch(e) { return false }
-} : function (te) {
- var range
- try {range = te.ownerDocument.selection.createRange()}
- catch(e) {}
- if (!range || range.parentElement() != te) { return false }
- return range.compareEndPoints("StartToEnd", range) != 0
-}
-
-var hasCopyEvent = (function () {
- var e = elt("div")
- if ("oncopy" in e) { return true }
- e.setAttribute("oncopy", "return;")
- return typeof e.oncopy == "function"
-})()
-
-var badZoomedRects = null
-function hasBadZoomedRects(measure) {
- if (badZoomedRects != null) { return badZoomedRects }
- var node = removeChildrenAndAdd(measure, elt("span", "x"))
- var normal = node.getBoundingClientRect()
- var fromRange = range(node, 0, 1).getBoundingClientRect()
- return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1
-}
-
-var modes = {};
-var mimeModes = {};
-// Extra arguments are stored as the mode's dependencies, which is
-// used by (legacy) mechanisms like loadmode.js to automatically
-// load a mode. (Preferred mechanism is the require/define calls.)
-function defineMode(name, mode) {
- if (arguments.length > 2)
- { mode.dependencies = Array.prototype.slice.call(arguments, 2) }
- modes[name] = mode
-}
-
-function defineMIME(mime, spec) {
- mimeModes[mime] = spec
-}
-
-// Given a MIME type, a {name, ...options} config object, or a name
-// string, return a mode config object.
-function resolveMode(spec) {
- if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
- spec = mimeModes[spec]
- } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
- var found = mimeModes[spec.name]
- if (typeof found == "string") { found = {name: found} }
- spec = createObj(found, spec)
- spec.name = found.name
- } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
- return resolveMode("application/xml")
- } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
- return resolveMode("application/json")
- }
- if (typeof spec == "string") { return {name: spec} }
- else { return spec || {name: "null"} }
-}
-
-// Given a mode spec (anything that resolveMode accepts), find and
-// initialize an actual mode object.
-function getMode(options, spec) {
- spec = resolveMode(spec)
- var mfactory = modes[spec.name]
- if (!mfactory) { return getMode(options, "text/plain") }
- var modeObj = mfactory(options, spec)
- if (modeExtensions.hasOwnProperty(spec.name)) {
- var exts = modeExtensions[spec.name]
- for (var prop in exts) {
- if (!exts.hasOwnProperty(prop)) { continue }
- if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] }
- modeObj[prop] = exts[prop]
- }
- }
- modeObj.name = spec.name
- if (spec.helperType) { modeObj.helperType = spec.helperType }
- if (spec.modeProps) { for (var prop$1 in spec.modeProps)
- { modeObj[prop$1] = spec.modeProps[prop$1] } }
-
- return modeObj
-}
-
-// This can be used to attach properties to mode objects from
-// outside the actual mode definition.
-var modeExtensions = {}
-function extendMode(mode, properties) {
- var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {})
- copyObj(properties, exts)
-}
-
-function copyState(mode, state) {
- if (state === true) { return state }
- if (mode.copyState) { return mode.copyState(state) }
- var nstate = {}
- for (var n in state) {
- var val = state[n]
- if (val instanceof Array) { val = val.concat([]) }
- nstate[n] = val
- }
- return nstate
-}
-
-// Given a mode and a state (for that mode), find the inner mode and
-// state at the position that the state refers to.
-function innerMode(mode, state) {
- var info
- while (mode.innerMode) {
- info = mode.innerMode(state)
- if (!info || info.mode == mode) { break }
- state = info.state
- mode = info.mode
- }
- return info || {mode: mode, state: state}
-}
-
-function startState(mode, a1, a2) {
- return mode.startState ? mode.startState(a1, a2) : true
-}
-
-// STRING STREAM
-
-// Fed to the mode parsers, provides helper functions to make
-// parsers more succinct.
-
-var StringStream = function(string, tabSize, lineOracle) {
- this.pos = this.start = 0
- this.string = string
- this.tabSize = tabSize || 8
- this.lastColumnPos = this.lastColumnValue = 0
- this.lineStart = 0
- this.lineOracle = lineOracle
-};
-
-StringStream.prototype.eol = function () {return this.pos >= this.string.length};
-StringStream.prototype.sol = function () {return this.pos == this.lineStart};
-StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};
-StringStream.prototype.next = function () {
- if (this.pos < this.string.length)
- { return this.string.charAt(this.pos++) }
-};
-StringStream.prototype.eat = function (match) {
- var ch = this.string.charAt(this.pos)
- var ok
- if (typeof match == "string") { ok = ch == match }
- else { ok = ch && (match.test ? match.test(ch) : match(ch)) }
- if (ok) {++this.pos; return ch}
-};
-StringStream.prototype.eatWhile = function (match) {
- var start = this.pos
- while (this.eat(match)){}
- return this.pos > start
-};
-StringStream.prototype.eatSpace = function () {
- var this$1 = this;
-
- var start = this.pos
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos }
- return this.pos > start
-};
-StringStream.prototype.skipToEnd = function () {this.pos = this.string.length};
-StringStream.prototype.skipTo = function (ch) {
- var found = this.string.indexOf(ch, this.pos)
- if (found > -1) {this.pos = found; return true}
-};
-StringStream.prototype.backUp = function (n) {this.pos -= n};
-StringStream.prototype.column = function () {
- if (this.lastColumnPos < this.start) {
- this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue)
- this.lastColumnPos = this.start
- }
- return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
-};
-StringStream.prototype.indentation = function () {
- return countColumn(this.string, null, this.tabSize) -
- (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)
-};
-StringStream.prototype.match = function (pattern, consume, caseInsensitive) {
- if (typeof pattern == "string") {
- var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }
- var substr = this.string.substr(this.pos, pattern.length)
- if (cased(substr) == cased(pattern)) {
- if (consume !== false) { this.pos += pattern.length }
- return true
- }
- } else {
- var match = this.string.slice(this.pos).match(pattern)
- if (match && match.index > 0) { return null }
- if (match && consume !== false) { this.pos += match[0].length }
- return match
- }
-};
-StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};
-StringStream.prototype.hideFirstChars = function (n, inner) {
- this.lineStart += n
- try { return inner() }
- finally { this.lineStart -= n }
-};
-StringStream.prototype.lookAhead = function (n) {
- var oracle = this.lineOracle
- return oracle && oracle.lookAhead(n)
-};
-
-var SavedContext = function(state, lookAhead) {
- this.state = state
- this.lookAhead = lookAhead
-};
-
-var Context = function(doc, state, line, lookAhead) {
- this.state = state
- this.doc = doc
- this.line = line
- this.maxLookAhead = lookAhead || 0
-};
-
-Context.prototype.lookAhead = function (n) {
- var line = this.doc.getLine(this.line + n)
- if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n }
- return line
-};
-
-Context.prototype.nextLine = function () {
- this.line++
- if (this.maxLookAhead > 0) { this.maxLookAhead-- }
-};
-
-Context.fromSaved = function (doc, saved, line) {
- if (saved instanceof SavedContext)
- { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }
- else
- { return new Context(doc, copyState(doc.mode, saved), line) }
-};
-
-Context.prototype.save = function (copy) {
- var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state
- return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state
-};
-
-
-// Compute a style array (an array starting with a mode generation
-// -- for invalidation -- followed by pairs of end positions and
-// style strings), which is used to highlight the tokens on the
-// line.
-function highlightLine(cm, line, context, forceToEnd) {
- // A styles array always starts with a number identifying the
- // mode/overlays that it is based on (for easy invalidation).
- var st = [cm.state.modeGen], lineClasses = {}
- // Compute the base array of styles
- runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },
- lineClasses, forceToEnd)
- var state = context.state
-
- // Run overlays, adjust style array.
- var loop = function ( o ) {
- var overlay = cm.state.overlays[o], i = 1, at = 0
- context.state = true
- runMode(cm, line.text, overlay.mode, context, function (end, style) {
- var start = i
- // Ensure there's a token end at the current position, and that i points at it
- while (at < end) {
- var i_end = st[i]
- if (i_end > end)
- { st.splice(i, 1, end, st[i+1], i_end) }
- i += 2
- at = Math.min(end, i_end)
- }
- if (!style) { return }
- if (overlay.opaque) {
- st.splice(start, i - start, end, "overlay " + style)
- i = start + 2
- } else {
- for (; start < i; start += 2) {
- var cur = st[start+1]
- st[start+1] = (cur ? cur + " " : "") + "overlay " + style
- }
- }
- }, lineClasses)
- };
-
- for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
- context.state = state
-
- return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
-}
-
-function getLineStyles(cm, line, updateFrontier) {
- if (!line.styles || line.styles[0] != cm.state.modeGen) {
- var context = getContextBefore(cm, lineNo(line))
- var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state)
- var result = highlightLine(cm, line, context)
- if (resetState) { context.state = resetState }
- line.stateAfter = context.save(!resetState)
- line.styles = result.styles
- if (result.classes) { line.styleClasses = result.classes }
- else if (line.styleClasses) { line.styleClasses = null }
- if (updateFrontier === cm.doc.highlightFrontier)
- { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) }
- }
- return line.styles
-}
-
-function getContextBefore(cm, n, precise) {
- var doc = cm.doc, display = cm.display
- if (!doc.mode.startState) { return new Context(doc, true, n) }
- var start = findStartLine(cm, n, precise)
- var saved = start > doc.first && getLine(doc, start - 1).stateAfter
- var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start)
-
- doc.iter(start, n, function (line) {
- processLine(cm, line.text, context)
- var pos = context.line
- line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null
- context.nextLine()
- })
- if (precise) { doc.modeFrontier = context.line }
- return context
-}
-
-// Lightweight form of highlight -- proceed over this line and
-// update state, but don't save a style array. Used for lines that
-// aren't currently visible.
-function processLine(cm, text, context, startAt) {
- var mode = cm.doc.mode
- var stream = new StringStream(text, cm.options.tabSize, context)
- stream.start = stream.pos = startAt || 0
- if (text == "") { callBlankLine(mode, context.state) }
- while (!stream.eol()) {
- readToken(mode, stream, context.state)
- stream.start = stream.pos
- }
-}
-
-function callBlankLine(mode, state) {
- if (mode.blankLine) { return mode.blankLine(state) }
- if (!mode.innerMode) { return }
- var inner = innerMode(mode, state)
- if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }
-}
-
-function readToken(mode, stream, state, inner) {
- for (var i = 0; i < 10; i++) {
- if (inner) { inner[0] = innerMode(mode, state).mode }
- var style = mode.token(stream, state)
- if (stream.pos > stream.start) { return style }
- }
- throw new Error("Mode " + mode.name + " failed to advance stream.")
-}
-
-var Token = function(stream, type, state) {
- this.start = stream.start; this.end = stream.pos
- this.string = stream.current()
- this.type = type || null
- this.state = state
-};
-
-// Utility for getTokenAt and getLineTokens
-function takeToken(cm, pos, precise, asArray) {
- var doc = cm.doc, mode = doc.mode, style
- pos = clipPos(doc, pos)
- var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise)
- var stream = new StringStream(line.text, cm.options.tabSize, context), tokens
- if (asArray) { tokens = [] }
- while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
- stream.start = stream.pos
- style = readToken(mode, stream, context.state)
- if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) }
- }
- return asArray ? tokens : new Token(stream, style, context.state)
-}
-
-function extractLineClasses(type, output) {
- if (type) { for (;;) {
- var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/)
- if (!lineClass) { break }
- type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length)
- var prop = lineClass[1] ? "bgClass" : "textClass"
- if (output[prop] == null)
- { output[prop] = lineClass[2] }
- else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
- { output[prop] += " " + lineClass[2] }
- } }
- return type
-}
-
-// Run the given mode's parser over a line, calling f for each token.
-function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {
- var flattenSpans = mode.flattenSpans
- if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans }
- var curStart = 0, curStyle = null
- var stream = new StringStream(text, cm.options.tabSize, context), style
- var inner = cm.options.addModeClass && [null]
- if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses) }
- while (!stream.eol()) {
- if (stream.pos > cm.options.maxHighlightLength) {
- flattenSpans = false
- if (forceToEnd) { processLine(cm, text, context, stream.pos) }
- stream.pos = text.length
- style = null
- } else {
- style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses)
- }
- if (inner) {
- var mName = inner[0].name
- if (mName) { style = "m-" + (style ? mName + " " + style : mName) }
- }
- if (!flattenSpans || curStyle != style) {
- while (curStart < stream.start) {
- curStart = Math.min(stream.start, curStart + 5000)
- f(curStart, curStyle)
- }
- curStyle = style
- }
- stream.start = stream.pos
- }
- while (curStart < stream.pos) {
- // Webkit seems to refuse to render text nodes longer than 57444
- // characters, and returns inaccurate measurements in nodes
- // starting around 5000 chars.
- var pos = Math.min(stream.pos, curStart + 5000)
- f(pos, curStyle)
- curStart = pos
- }
-}
-
-// Finds the line to start with when starting a parse. Tries to
-// find a line with a stateAfter, so that it can start with a
-// valid state. If that fails, it returns the line with the
-// smallest indentation, which tends to need the least context to
-// parse correctly.
-function findStartLine(cm, n, precise) {
- var minindent, minline, doc = cm.doc
- var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100)
- for (var search = n; search > lim; --search) {
- if (search <= doc.first) { return doc.first }
- var line = getLine(doc, search - 1), after = line.stateAfter
- if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))
- { return search }
- var indented = countColumn(line.text, null, cm.options.tabSize)
- if (minline == null || minindent > indented) {
- minline = search - 1
- minindent = indented
- }
- }
- return minline
-}
-
-function retreatFrontier(doc, n) {
- doc.modeFrontier = Math.min(doc.modeFrontier, n)
- if (doc.highlightFrontier < n - 10) { return }
- var start = doc.first
- for (var line = n - 1; line > start; line--) {
- var saved = getLine(doc, line).stateAfter
- // change is on 3
- // state on line 1 looked ahead 2 -- so saw 3
- // test 1 + 2 < 3 should cover this
- if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {
- start = line + 1
- break
- }
- }
- doc.highlightFrontier = Math.min(doc.highlightFrontier, start)
-}
-
-// LINE DATA STRUCTURE
-
-// Line objects. These hold state related to a line, including
-// highlighting info (the styles array).
-var Line = function(text, markedSpans, estimateHeight) {
- this.text = text
- attachMarkedSpans(this, markedSpans)
- this.height = estimateHeight ? estimateHeight(this) : 1
-};
-
-Line.prototype.lineNo = function () { return lineNo(this) };
-eventMixin(Line)
-
-// Change the content (text, markers) of a line. Automatically
-// invalidates cached information and tries to re-estimate the
-// line's height.
-function updateLine(line, text, markedSpans, estimateHeight) {
- line.text = text
- if (line.stateAfter) { line.stateAfter = null }
- if (line.styles) { line.styles = null }
- if (line.order != null) { line.order = null }
- detachMarkedSpans(line)
- attachMarkedSpans(line, markedSpans)
- var estHeight = estimateHeight ? estimateHeight(line) : 1
- if (estHeight != line.height) { updateLineHeight(line, estHeight) }
-}
-
-// Detach a line from the document tree and its markers.
-function cleanUpLine(line) {
- line.parent = null
- detachMarkedSpans(line)
-}
-
-// Convert a style as returned by a mode (either null, or a string
-// containing one or more styles) to a CSS style. This is cached,
-// and also looks for line-wide styles.
-var styleToClassCache = {};
-var styleToClassCacheWithMode = {};
-function interpretTokenStyle(style, options) {
- if (!style || /^\s*$/.test(style)) { return null }
- var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache
- return cache[style] ||
- (cache[style] = style.replace(/\S+/g, "cm-$&"))
-}
-
-// Render the DOM representation of the text of a line. Also builds
-// up a 'line map', which points at the DOM nodes that represent
-// specific stretches of text, and is used by the measuring code.
-// The returned object contains the DOM node, this map, and
-// information about line-wide styles that were set by the mode.
-function buildLineContent(cm, lineView) {
- // The padding-right forces the element to have a 'border', which
- // is needed on Webkit to be able to get line-level bounding
- // rectangles for it (in measureChar).
- var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null)
- var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content,
- col: 0, pos: 0, cm: cm,
- trailingSpace: false,
- splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}
- lineView.measure = {}
-
- // Iterate over the logical lines that make up this visual line.
- for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
- var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0)
- builder.pos = 0
- builder.addToken = buildToken
- // Optionally wire in some hacks into the token-rendering
- // algorithm, to deal with browser quirks.
- if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))
- { builder.addToken = buildTokenBadBidi(builder.addToken, order) }
- builder.map = []
- var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line)
- insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate))
- if (line.styleClasses) {
- if (line.styleClasses.bgClass)
- { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") }
- if (line.styleClasses.textClass)
- { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") }
- }
-
- // Ensure at least a single node is present, for measuring.
- if (builder.map.length == 0)
- { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) }
-
- // Store the map and a cache object for the current logical line
- if (i == 0) {
- lineView.measure.map = builder.map
- lineView.measure.cache = {}
- } else {
- ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)
- ;(lineView.measure.caches || (lineView.measure.caches = [])).push({})
- }
- }
-
- // See issue #2901
- if (webkit) {
- var last = builder.content.lastChild
- if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
- { builder.content.className = "cm-tab-wrap-hack" }
- }
-
- signal(cm, "renderLine", cm, lineView.line, builder.pre)
- if (builder.pre.className)
- { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") }
-
- return builder
-}
-
-function defaultSpecialCharPlaceholder(ch) {
- var token = elt("span", "\u2022", "cm-invalidchar")
- token.title = "\\u" + ch.charCodeAt(0).toString(16)
- token.setAttribute("aria-label", token.title)
- return token
-}
-
-// Build up the DOM representation for a single token, and add it to
-// the line map. Takes care to render special characters separately.
-function buildToken(builder, text, style, startStyle, endStyle, title, css) {
- if (!text) { return }
- var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text
- var special = builder.cm.state.specialChars, mustWrap = false
- var content
- if (!special.test(text)) {
- builder.col += text.length
- content = document.createTextNode(displayText)
- builder.map.push(builder.pos, builder.pos + text.length, content)
- if (ie && ie_version < 9) { mustWrap = true }
- builder.pos += text.length
- } else {
- content = document.createDocumentFragment()
- var pos = 0
- while (true) {
- special.lastIndex = pos
- var m = special.exec(text)
- var skipped = m ? m.index - pos : text.length - pos
- if (skipped) {
- var txt = document.createTextNode(displayText.slice(pos, pos + skipped))
- if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) }
- else { content.appendChild(txt) }
- builder.map.push(builder.pos, builder.pos + skipped, txt)
- builder.col += skipped
- builder.pos += skipped
- }
- if (!m) { break }
- pos += skipped + 1
- var txt$1 = (void 0)
- if (m[0] == "\t") {
- var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize
- txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"))
- txt$1.setAttribute("role", "presentation")
- txt$1.setAttribute("cm-text", "\t")
- builder.col += tabWidth
- } else if (m[0] == "\r" || m[0] == "\n") {
- txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"))
- txt$1.setAttribute("cm-text", m[0])
- builder.col += 1
- } else {
- txt$1 = builder.cm.options.specialCharPlaceholder(m[0])
- txt$1.setAttribute("cm-text", m[0])
- if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) }
- else { content.appendChild(txt$1) }
- builder.col += 1
- }
- builder.map.push(builder.pos, builder.pos + 1, txt$1)
- builder.pos++
- }
- }
- builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
- if (style || startStyle || endStyle || mustWrap || css) {
- var fullStyle = style || ""
- if (startStyle) { fullStyle += startStyle }
- if (endStyle) { fullStyle += endStyle }
- var token = elt("span", [content], fullStyle, css)
- if (title) { token.title = title }
- return builder.content.appendChild(token)
- }
- builder.content.appendChild(content)
-}
-
-function splitSpaces(text, trailingBefore) {
- if (text.length > 1 && !/ /.test(text)) { return text }
- var spaceBefore = trailingBefore, result = ""
- for (var i = 0; i < text.length; i++) {
- var ch = text.charAt(i)
- if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
- { ch = "\u00a0" }
- result += ch
- spaceBefore = ch == " "
- }
- return result
-}
-
-// Work around nonsense dimensions being reported for stretches of
-// right-to-left text.
-function buildTokenBadBidi(inner, order) {
- return function (builder, text, style, startStyle, endStyle, title, css) {
- style = style ? style + " cm-force-border" : "cm-force-border"
- var start = builder.pos, end = start + text.length
- for (;;) {
- // Find the part that overlaps with the start of this text
- var part = (void 0)
- for (var i = 0; i < order.length; i++) {
- part = order[i]
- if (part.to > start && part.from <= start) { break }
- }
- if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) }
- inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css)
- startStyle = null
- text = text.slice(part.to - start)
- start = part.to
- }
- }
-}
-
-function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
- var widget = !ignoreWidget && marker.widgetNode
- if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) }
- if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
- if (!widget)
- { widget = builder.content.appendChild(document.createElement("span")) }
- widget.setAttribute("cm-marker", marker.id)
- }
- if (widget) {
- builder.cm.display.input.setUneditable(widget)
- builder.content.appendChild(widget)
- }
- builder.pos += size
- builder.trailingSpace = false
-}
-
-// Outputs a number of spans to make up a line, taking highlighting
-// and marked text into account.
-function insertLineContent(line, builder, styles) {
- var spans = line.markedSpans, allText = line.text, at = 0
- if (!spans) {
- for (var i$1 = 1; i$1 < styles.length; i$1+=2)
- { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) }
- return
- }
-
- var len = allText.length, pos = 0, i = 1, text = "", style, css
- var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed
- for (;;) {
- if (nextChange == pos) { // Update current marker set
- spanStyle = spanEndStyle = spanStartStyle = title = css = ""
- collapsed = null; nextChange = Infinity
- var foundBookmarks = [], endStyles = (void 0)
- for (var j = 0; j < spans.length; ++j) {
- var sp = spans[j], m = sp.marker
- if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
- foundBookmarks.push(m)
- } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
- if (sp.to != null && sp.to != pos && nextChange > sp.to) {
- nextChange = sp.to
- spanEndStyle = ""
- }
- if (m.className) { spanStyle += " " + m.className }
- if (m.css) { css = (css ? css + ";" : "") + m.css }
- if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle }
- if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) }
- if (m.title && !title) { title = m.title }
- if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
- { collapsed = sp }
- } else if (sp.from > pos && nextChange > sp.from) {
- nextChange = sp.from
- }
- }
- if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)
- { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } }
-
- if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)
- { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } }
- if (collapsed && (collapsed.from || 0) == pos) {
- buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
- collapsed.marker, collapsed.from == null)
- if (collapsed.to == null) { return }
- if (collapsed.to == pos) { collapsed = false }
- }
- }
- if (pos >= len) { break }
-
- var upto = Math.min(len, nextChange)
- while (true) {
- if (text) {
- var end = pos + text.length
- if (!collapsed) {
- var tokenText = end > upto ? text.slice(0, upto - pos) : text
- builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
- spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css)
- }
- if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}
- pos = end
- spanStartStyle = ""
- }
- text = allText.slice(at, at = styles[i++])
- style = interpretTokenStyle(styles[i++], builder.cm.options)
- }
- }
-}
-
-
-// These objects are used to represent the visible (currently drawn)
-// part of the document. A LineView may correspond to multiple
-// logical lines, if those are connected by collapsed ranges.
-function LineView(doc, line, lineN) {
- // The starting line
- this.line = line
- // Continuing lines, if any
- this.rest = visualLineContinued(line)
- // Number of logical lines in this visual line
- this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1
- this.node = this.text = null
- this.hidden = lineIsHidden(doc, line)
-}
-
-// Create a range of LineView objects for the given lines.
-function buildViewArray(cm, from, to) {
- var array = [], nextPos
- for (var pos = from; pos < to; pos = nextPos) {
- var view = new LineView(cm.doc, getLine(cm.doc, pos), pos)
- nextPos = pos + view.size
- array.push(view)
- }
- return array
-}
-
-var operationGroup = null
-
-function pushOperation(op) {
- if (operationGroup) {
- operationGroup.ops.push(op)
- } else {
- op.ownsGroup = operationGroup = {
- ops: [op],
- delayedCallbacks: []
- }
- }
-}
-
-function fireCallbacksForOps(group) {
- // Calls delayed callbacks and cursorActivity handlers until no
- // new ones appear
- var callbacks = group.delayedCallbacks, i = 0
- do {
- for (; i < callbacks.length; i++)
- { callbacks[i].call(null) }
- for (var j = 0; j < group.ops.length; j++) {
- var op = group.ops[j]
- if (op.cursorActivityHandlers)
- { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
- { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } }
- }
- } while (i < callbacks.length)
-}
-
-function finishOperation(op, endCb) {
- var group = op.ownsGroup
- if (!group) { return }
-
- try { fireCallbacksForOps(group) }
- finally {
- operationGroup = null
- endCb(group)
- }
-}
-
-var orphanDelayedCallbacks = null
-
-// Often, we want to signal events at a point where we are in the
-// middle of some work, but don't want the handler to start calling
-// other methods on the editor, which might be in an inconsistent
-// state or simply not expect any other events to happen.
-// signalLater looks whether there are any handlers, and schedules
-// them to be executed when the last operation ends, or, if no
-// operation is active, when a timeout fires.
-function signalLater(emitter, type /*, values...*/) {
- var arr = getHandlers(emitter, type)
- if (!arr.length) { return }
- var args = Array.prototype.slice.call(arguments, 2), list
- if (operationGroup) {
- list = operationGroup.delayedCallbacks
- } else if (orphanDelayedCallbacks) {
- list = orphanDelayedCallbacks
- } else {
- list = orphanDelayedCallbacks = []
- setTimeout(fireOrphanDelayed, 0)
- }
- var loop = function ( i ) {
- list.push(function () { return arr[i].apply(null, args); })
- };
-
- for (var i = 0; i < arr.length; ++i)
- loop( i );
-}
-
-function fireOrphanDelayed() {
- var delayed = orphanDelayedCallbacks
- orphanDelayedCallbacks = null
- for (var i = 0; i < delayed.length; ++i) { delayed[i]() }
-}
-
-// When an aspect of a line changes, a string is added to
-// lineView.changes. This updates the relevant part of the line's
-// DOM structure.
-function updateLineForChanges(cm, lineView, lineN, dims) {
- for (var j = 0; j < lineView.changes.length; j++) {
- var type = lineView.changes[j]
- if (type == "text") { updateLineText(cm, lineView) }
- else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) }
- else if (type == "class") { updateLineClasses(cm, lineView) }
- else if (type == "widget") { updateLineWidgets(cm, lineView, dims) }
- }
- lineView.changes = null
-}
-
-// Lines with gutter elements, widgets or a background class need to
-// be wrapped, and have the extra elements added to the wrapper div
-function ensureLineWrapped(lineView) {
- if (lineView.node == lineView.text) {
- lineView.node = elt("div", null, null, "position: relative")
- if (lineView.text.parentNode)
- { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) }
- lineView.node.appendChild(lineView.text)
- if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 }
- }
- return lineView.node
-}
-
-function updateLineBackground(cm, lineView) {
- var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass
- if (cls) { cls += " CodeMirror-linebackground" }
- if (lineView.background) {
- if (cls) { lineView.background.className = cls }
- else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null }
- } else if (cls) {
- var wrap = ensureLineWrapped(lineView)
- lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild)
- cm.display.input.setUneditable(lineView.background)
- }
-}
-
-// Wrapper around buildLineContent which will reuse the structure
-// in display.externalMeasured when possible.
-function getLineContent(cm, lineView) {
- var ext = cm.display.externalMeasured
- if (ext && ext.line == lineView.line) {
- cm.display.externalMeasured = null
- lineView.measure = ext.measure
- return ext.built
- }
- return buildLineContent(cm, lineView)
-}
-
-// Redraw the line's text. Interacts with the background and text
-// classes because the mode may output tokens that influence these
-// classes.
-function updateLineText(cm, lineView) {
- var cls = lineView.text.className
- var built = getLineContent(cm, lineView)
- if (lineView.text == lineView.node) { lineView.node = built.pre }
- lineView.text.parentNode.replaceChild(built.pre, lineView.text)
- lineView.text = built.pre
- if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
- lineView.bgClass = built.bgClass
- lineView.textClass = built.textClass
- updateLineClasses(cm, lineView)
- } else if (cls) {
- lineView.text.className = cls
- }
-}
-
-function updateLineClasses(cm, lineView) {
- updateLineBackground(cm, lineView)
- if (lineView.line.wrapClass)
- { ensureLineWrapped(lineView).className = lineView.line.wrapClass }
- else if (lineView.node != lineView.text)
- { lineView.node.className = "" }
- var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass
- lineView.text.className = textClass || ""
-}
-
-function updateLineGutter(cm, lineView, lineN, dims) {
- if (lineView.gutter) {
- lineView.node.removeChild(lineView.gutter)
- lineView.gutter = null
- }
- if (lineView.gutterBackground) {
- lineView.node.removeChild(lineView.gutterBackground)
- lineView.gutterBackground = null
- }
- if (lineView.line.gutterClass) {
- var wrap = ensureLineWrapped(lineView)
- lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
- ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px"))
- cm.display.input.setUneditable(lineView.gutterBackground)
- wrap.insertBefore(lineView.gutterBackground, lineView.text)
- }
- var markers = lineView.line.gutterMarkers
- if (cm.options.lineNumbers || markers) {
- var wrap$1 = ensureLineWrapped(lineView)
- var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"))
- cm.display.input.setUneditable(gutterWrap)
- wrap$1.insertBefore(gutterWrap, lineView.text)
- if (lineView.line.gutterClass)
- { gutterWrap.className += " " + lineView.line.gutterClass }
- if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
- { lineView.lineNumber = gutterWrap.appendChild(
- elt("div", lineNumberFor(cm.options, lineN),
- "CodeMirror-linenumber CodeMirror-gutter-elt",
- ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) }
- if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) {
- var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]
- if (found)
- { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
- ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) }
- } }
- }
-}
-
-function updateLineWidgets(cm, lineView, dims) {
- if (lineView.alignable) { lineView.alignable = null }
- for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {
- next = node.nextSibling
- if (node.className == "CodeMirror-linewidget")
- { lineView.node.removeChild(node) }
- }
- insertLineWidgets(cm, lineView, dims)
-}
-
-// Build a line's DOM representation from scratch
-function buildLineElement(cm, lineView, lineN, dims) {
- var built = getLineContent(cm, lineView)
- lineView.text = lineView.node = built.pre
- if (built.bgClass) { lineView.bgClass = built.bgClass }
- if (built.textClass) { lineView.textClass = built.textClass }
-
- updateLineClasses(cm, lineView)
- updateLineGutter(cm, lineView, lineN, dims)
- insertLineWidgets(cm, lineView, dims)
- return lineView.node
-}
-
-// A lineView may contain multiple logical lines (when merged by
-// collapsed spans). The widgets for all of them need to be drawn.
-function insertLineWidgets(cm, lineView, dims) {
- insertLineWidgetsFor(cm, lineView.line, lineView, dims, true)
- if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
- { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } }
-}
-
-function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
- if (!line.widgets) { return }
- var wrap = ensureLineWrapped(lineView)
- for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
- var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget")
- if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") }
- positionLineWidget(widget, node, lineView, dims)
- cm.display.input.setUneditable(node)
- if (allowAbove && widget.above)
- { wrap.insertBefore(node, lineView.gutter || lineView.text) }
- else
- { wrap.appendChild(node) }
- signalLater(widget, "redraw")
- }
-}
-
-function positionLineWidget(widget, node, lineView, dims) {
- if (widget.noHScroll) {
- ;(lineView.alignable || (lineView.alignable = [])).push(node)
- var width = dims.wrapperWidth
- node.style.left = dims.fixedPos + "px"
- if (!widget.coverGutter) {
- width -= dims.gutterTotalWidth
- node.style.paddingLeft = dims.gutterTotalWidth + "px"
- }
- node.style.width = width + "px"
- }
- if (widget.coverGutter) {
- node.style.zIndex = 5
- node.style.position = "relative"
- if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" }
- }
-}
-
-function widgetHeight(widget) {
- if (widget.height != null) { return widget.height }
- var cm = widget.doc.cm
- if (!cm) { return 0 }
- if (!contains(document.body, widget.node)) {
- var parentStyle = "position: relative;"
- if (widget.coverGutter)
- { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" }
- if (widget.noHScroll)
- { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" }
- removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle))
- }
- return widget.height = widget.node.parentNode.offsetHeight
-}
-
-// Return true when the given mouse event happened in a widget
-function eventInWidget(display, e) {
- for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
- if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
- (n.parentNode == display.sizer && n != display.mover))
- { return true }
- }
-}
-
-// POSITION MEASUREMENT
-
-function paddingTop(display) {return display.lineSpace.offsetTop}
-function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}
-function paddingH(display) {
- if (display.cachedPaddingH) { return display.cachedPaddingH }
- var e = removeChildrenAndAdd(display.measure, elt("pre", "x"))
- var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle
- var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}
- if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data }
- return data
-}
-
-function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }
-function displayWidth(cm) {
- return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
-}
-function displayHeight(cm) {
- return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight
-}
-
-// Ensure the lineView.wrapping.heights array is populated. This is
-// an array of bottom offsets for the lines that make up a drawn
-// line. When lineWrapping is on, there might be more than one
-// height.
-function ensureLineHeights(cm, lineView, rect) {
- var wrapping = cm.options.lineWrapping
- var curWidth = wrapping && displayWidth(cm)
- if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
- var heights = lineView.measure.heights = []
- if (wrapping) {
- lineView.measure.width = curWidth
- var rects = lineView.text.firstChild.getClientRects()
- for (var i = 0; i < rects.length - 1; i++) {
- var cur = rects[i], next = rects[i + 1]
- if (Math.abs(cur.bottom - next.bottom) > 2)
- { heights.push((cur.bottom + next.top) / 2 - rect.top) }
- }
- }
- heights.push(rect.bottom - rect.top)
- }
-}
-
-// Find a line map (mapping character offsets to text nodes) and a
-// measurement cache for the given line number. (A line view might
-// contain multiple lines when collapsed ranges are present.)
-function mapFromLineView(lineView, line, lineN) {
- if (lineView.line == line)
- { return {map: lineView.measure.map, cache: lineView.measure.cache} }
- for (var i = 0; i < lineView.rest.length; i++)
- { if (lineView.rest[i] == line)
- { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }
- for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
- { if (lineNo(lineView.rest[i$1]) > lineN)
- { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }
-}
-
-// Render a line into the hidden node display.externalMeasured. Used
-// when measurement is needed for a line that's not in the viewport.
-function updateExternalMeasurement(cm, line) {
- line = visualLine(line)
- var lineN = lineNo(line)
- var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN)
- view.lineN = lineN
- var built = view.built = buildLineContent(cm, view)
- view.text = built.pre
- removeChildrenAndAdd(cm.display.lineMeasure, built.pre)
- return view
-}
-
-// Get a {top, bottom, left, right} box (in line-local coordinates)
-// for a given character.
-function measureChar(cm, line, ch, bias) {
- return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)
-}
-
-// Find a line view that corresponds to the given line number.
-function findViewForLine(cm, lineN) {
- if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
- { return cm.display.view[findViewIndex(cm, lineN)] }
- var ext = cm.display.externalMeasured
- if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
- { return ext }
-}
-
-// Measurement can be split in two steps, the set-up work that
-// applies to the whole line, and the measurement of the actual
-// character. Functions like coordsChar, that need to do a lot of
-// measurements in a row, can thus ensure that the set-up work is
-// only done once.
-function prepareMeasureForLine(cm, line) {
- var lineN = lineNo(line)
- var view = findViewForLine(cm, lineN)
- if (view && !view.text) {
- view = null
- } else if (view && view.changes) {
- updateLineForChanges(cm, view, lineN, getDimensions(cm))
- cm.curOp.forceUpdate = true
- }
- if (!view)
- { view = updateExternalMeasurement(cm, line) }
-
- var info = mapFromLineView(view, line, lineN)
- return {
- line: line, view: view, rect: null,
- map: info.map, cache: info.cache, before: info.before,
- hasHeights: false
- }
-}
-
-// Given a prepared measurement object, measures the position of an
-// actual character (or fetches it from the cache).
-function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
- if (prepared.before) { ch = -1 }
- var key = ch + (bias || ""), found
- if (prepared.cache.hasOwnProperty(key)) {
- found = prepared.cache[key]
- } else {
- if (!prepared.rect)
- { prepared.rect = prepared.view.text.getBoundingClientRect() }
- if (!prepared.hasHeights) {
- ensureLineHeights(cm, prepared.view, prepared.rect)
- prepared.hasHeights = true
- }
- found = measureCharInner(cm, prepared, ch, bias)
- if (!found.bogus) { prepared.cache[key] = found }
- }
- return {left: found.left, right: found.right,
- top: varHeight ? found.rtop : found.top,
- bottom: varHeight ? found.rbottom : found.bottom}
-}
-
-var nullRect = {left: 0, right: 0, top: 0, bottom: 0}
-
-function nodeAndOffsetInLineMap(map, ch, bias) {
- var node, start, end, collapse, mStart, mEnd
- // First, search the line map for the text node corresponding to,
- // or closest to, the target character.
- for (var i = 0; i < map.length; i += 3) {
- mStart = map[i]
- mEnd = map[i + 1]
- if (ch < mStart) {
- start = 0; end = 1
- collapse = "left"
- } else if (ch < mEnd) {
- start = ch - mStart
- end = start + 1
- } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
- end = mEnd - mStart
- start = end - 1
- if (ch >= mEnd) { collapse = "right" }
- }
- if (start != null) {
- node = map[i + 2]
- if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
- { collapse = bias }
- if (bias == "left" && start == 0)
- { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
- node = map[(i -= 3) + 2]
- collapse = "left"
- } }
- if (bias == "right" && start == mEnd - mStart)
- { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
- node = map[(i += 3) + 2]
- collapse = "right"
- } }
- break
- }
- }
- return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}
-}
-
-function getUsefulRect(rects, bias) {
- var rect = nullRect
- if (bias == "left") { for (var i = 0; i < rects.length; i++) {
- if ((rect = rects[i]).left != rect.right) { break }
- } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {
- if ((rect = rects[i$1]).left != rect.right) { break }
- } }
- return rect
-}
-
-function measureCharInner(cm, prepared, ch, bias) {
- var place = nodeAndOffsetInLineMap(prepared.map, ch, bias)
- var node = place.node, start = place.start, end = place.end, collapse = place.collapse
-
- var rect
- if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
- for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned
- while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start }
- while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end }
- if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)
- { rect = node.parentNode.getBoundingClientRect() }
- else
- { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) }
- if (rect.left || rect.right || start == 0) { break }
- end = start
- start = start - 1
- collapse = "right"
- }
- if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) }
- } else { // If it is a widget, simply get the box for the whole widget.
- if (start > 0) { collapse = bias = "right" }
- var rects
- if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
- { rect = rects[bias == "right" ? rects.length - 1 : 0] }
- else
- { rect = node.getBoundingClientRect() }
- }
- if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
- var rSpan = node.parentNode.getClientRects()[0]
- if (rSpan)
- { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} }
- else
- { rect = nullRect }
- }
-
- var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top
- var mid = (rtop + rbot) / 2
- var heights = prepared.view.measure.heights
- var i = 0
- for (; i < heights.length - 1; i++)
- { if (mid < heights[i]) { break } }
- var top = i ? heights[i - 1] : 0, bot = heights[i]
- var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
- right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
- top: top, bottom: bot}
- if (!rect.left && !rect.right) { result.bogus = true }
- if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot }
-
- return result
-}
-
-// Work around problem with bounding client rects on ranges being
-// returned incorrectly when zoomed on IE10 and below.
-function maybeUpdateRectForZooming(measure, rect) {
- if (!window.screen || screen.logicalXDPI == null ||
- screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
- { return rect }
- var scaleX = screen.logicalXDPI / screen.deviceXDPI
- var scaleY = screen.logicalYDPI / screen.deviceYDPI
- return {left: rect.left * scaleX, right: rect.right * scaleX,
- top: rect.top * scaleY, bottom: rect.bottom * scaleY}
-}
-
-function clearLineMeasurementCacheFor(lineView) {
- if (lineView.measure) {
- lineView.measure.cache = {}
- lineView.measure.heights = null
- if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
- { lineView.measure.caches[i] = {} } }
- }
-}
-
-function clearLineMeasurementCache(cm) {
- cm.display.externalMeasure = null
- removeChildren(cm.display.lineMeasure)
- for (var i = 0; i < cm.display.view.length; i++)
- { clearLineMeasurementCacheFor(cm.display.view[i]) }
-}
-
-function clearCaches(cm) {
- clearLineMeasurementCache(cm)
- cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null
- if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true }
- cm.display.lineNumChars = null
-}
-
-function pageScrollX() {
- // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206
- // which causes page_Offset and bounding client rects to use
- // different reference viewports and invalidate our calculations.
- if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }
- return window.pageXOffset || (document.documentElement || document.body).scrollLeft
-}
-function pageScrollY() {
- if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }
- return window.pageYOffset || (document.documentElement || document.body).scrollTop
-}
-
-// Converts a {top, bottom, left, right} box from line-local
-// coordinates into another coordinate system. Context may be one of
-// "line", "div" (display.lineDiv), "local"./null (editor), "window",
-// or "page".
-function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
- if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) {
- var size = widgetHeight(lineObj.widgets[i])
- rect.top += size; rect.bottom += size
- } } }
- if (context == "line") { return rect }
- if (!context) { context = "local" }
- var yOff = heightAtLine(lineObj)
- if (context == "local") { yOff += paddingTop(cm.display) }
- else { yOff -= cm.display.viewOffset }
- if (context == "page" || context == "window") {
- var lOff = cm.display.lineSpace.getBoundingClientRect()
- yOff += lOff.top + (context == "window" ? 0 : pageScrollY())
- var xOff = lOff.left + (context == "window" ? 0 : pageScrollX())
- rect.left += xOff; rect.right += xOff
- }
- rect.top += yOff; rect.bottom += yOff
- return rect
-}
-
-// Coverts a box from "div" coords to another coordinate system.
-// Context may be "window", "page", "div", or "local"./null.
-function fromCoordSystem(cm, coords, context) {
- if (context == "div") { return coords }
- var left = coords.left, top = coords.top
- // First move into "page" coordinate system
- if (context == "page") {
- left -= pageScrollX()
- top -= pageScrollY()
- } else if (context == "local" || !context) {
- var localBox = cm.display.sizer.getBoundingClientRect()
- left += localBox.left
- top += localBox.top
- }
-
- var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect()
- return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}
-}
-
-function charCoords(cm, pos, context, lineObj, bias) {
- if (!lineObj) { lineObj = getLine(cm.doc, pos.line) }
- return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)
-}
-
-// Returns a box for a given cursor position, which may have an
-// 'other' property containing the position of the secondary cursor
-// on a bidi boundary.
-// A cursor Pos(line, char, "before") is on the same visual line as `char - 1`
-// and after `char - 1` in writing order of `char - 1`
-// A cursor Pos(line, char, "after") is on the same visual line as `char`
-// and before `char` in writing order of `char`
-// Examples (upper-case letters are RTL, lower-case are LTR):
-// Pos(0, 1, ...)
-// before after
-// ab a|b a|b
-// aB a|B aB|
-// Ab |Ab A|b
-// AB B|A B|A
-// Every position after the last character on a line is considered to stick
-// to the last character on the line.
-function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
- lineObj = lineObj || getLine(cm.doc, pos.line)
- if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) }
- function get(ch, right) {
- var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight)
- if (right) { m.left = m.right; } else { m.right = m.left }
- return intoCoordSystem(cm, lineObj, m, context)
- }
- var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky
- if (ch >= lineObj.text.length) {
- ch = lineObj.text.length
- sticky = "before"
- } else if (ch <= 0) {
- ch = 0
- sticky = "after"
- }
- if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") }
-
- function getBidi(ch, partPos, invert) {
- var part = order[partPos], right = (part.level % 2) != 0
- return get(invert ? ch - 1 : ch, right != invert)
- }
- var partPos = getBidiPartAt(order, ch, sticky)
- var other = bidiOther
- var val = getBidi(ch, partPos, sticky == "before")
- if (other != null) { val.other = getBidi(ch, other, sticky != "before") }
- return val
-}
-
-// Used to cheaply estimate the coordinates for a position. Used for
-// intermediate scroll updates.
-function estimateCoords(cm, pos) {
- var left = 0
- pos = clipPos(cm.doc, pos)
- if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch }
- var lineObj = getLine(cm.doc, pos.line)
- var top = heightAtLine(lineObj) + paddingTop(cm.display)
- return {left: left, right: left, top: top, bottom: top + lineObj.height}
-}
-
-// Positions returned by coordsChar contain some extra information.
-// xRel is the relative x position of the input coordinates compared
-// to the found position (so xRel > 0 means the coordinates are to
-// the right of the character position, for example). When outside
-// is true, that means the coordinates lie outside the line's
-// vertical range.
-function PosWithInfo(line, ch, sticky, outside, xRel) {
- var pos = Pos(line, ch, sticky)
- pos.xRel = xRel
- if (outside) { pos.outside = true }
- return pos
-}
-
-// Compute the character position closest to the given coordinates.
-// Input must be lineSpace-local ("div" coordinate system).
-function coordsChar(cm, x, y) {
- var doc = cm.doc
- y += cm.display.viewOffset
- if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) }
- var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1
- if (lineN > last)
- { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) }
- if (x < 0) { x = 0 }
-
- var lineObj = getLine(doc, lineN)
- for (;;) {
- var found = coordsCharInner(cm, lineObj, lineN, x, y)
- var merged = collapsedSpanAtEnd(lineObj)
- var mergedPos = merged && merged.find(0, true)
- if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
- { lineN = lineNo(lineObj = mergedPos.to.line) }
- else
- { return found }
- }
-}
-
-function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
- var measure = function (ch) { return intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line"); }
- var end = lineObj.text.length
- var begin = findFirst(function (ch) { return measure(ch - 1).bottom <= y; }, end, 0)
- end = findFirst(function (ch) { return measure(ch).top > y; }, begin, end)
- return {begin: begin, end: end}
-}
-
-function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
- var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top
- return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
-}
-
-function coordsCharInner(cm, lineObj, lineNo, x, y) {
- y -= heightAtLine(lineObj)
- var begin = 0, end = lineObj.text.length
- var preparedMeasure = prepareMeasureForLine(cm, lineObj)
- var pos
- var order = getOrder(lineObj, cm.doc.direction)
- if (order) {
- if (cm.options.lineWrapping) {
- ;var assign;
- ((assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = assign.begin, end = assign.end, assign))
- }
- pos = new Pos(lineNo, Math.floor(begin + (end - begin) / 2))
- var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left
- var dir = beginLeft < x ? 1 : -1
- var prevDiff, diff = beginLeft - x, prevPos
- var steps = Math.ceil((end - begin) / 4)
- outer: do {
- prevDiff = diff
- prevPos = pos
- var i = 0
- for (; i < steps; ++i) {
- var prevPos$1 = pos
- pos = moveVisually(cm, lineObj, pos, dir)
- if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos.ch - 1 : pos.ch)) {
- pos = prevPos$1
- break outer
- }
- }
- diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x
- if (steps > 1) {
- var diff_change_per_step = Math.abs(diff - prevDiff) / steps
- steps = Math.min(steps, Math.ceil(Math.abs(diff) / diff_change_per_step))
- dir = diff < 0 ? 1 : -1
- }
- } while (diff != 0 && (steps > 1 || ((dir < 0) != (diff < 0) && (Math.abs(diff) <= Math.abs(prevDiff)))))
- if (Math.abs(diff) > Math.abs(prevDiff)) {
- if ((diff < 0) == (prevDiff < 0)) { throw new Error("Broke out of infinite loop in coordsCharInner") }
- pos = prevPos
- }
- } else {
- var ch = findFirst(function (ch) {
- var box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line")
- if (box.top > y) {
- // For the cursor stickiness
- end = Math.min(ch, end)
- return true
- }
- else if (box.bottom <= y) { return false }
- else if (box.left > x) { return true }
- else if (box.right < x) { return false }
- else { return (x - box.left < box.right - x) }
- }, begin, end)
- ch = skipExtendingChars(lineObj.text, ch, 1)
- pos = new Pos(lineNo, ch, ch == end ? "before" : "after")
- }
- var coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure)
- if (y < coords.top || coords.bottom < y) { pos.outside = true }
- pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0)
- return pos
-}
-
-var measureText
-// Compute the default text height.
-function textHeight(display) {
- if (display.cachedTextHeight != null) { return display.cachedTextHeight }
- if (measureText == null) {
- measureText = elt("pre")
- // Measure a bunch of lines, for browsers that compute
- // fractional heights.
- for (var i = 0; i < 49; ++i) {
- measureText.appendChild(document.createTextNode("x"))
- measureText.appendChild(elt("br"))
- }
- measureText.appendChild(document.createTextNode("x"))
- }
- removeChildrenAndAdd(display.measure, measureText)
- var height = measureText.offsetHeight / 50
- if (height > 3) { display.cachedTextHeight = height }
- removeChildren(display.measure)
- return height || 1
-}
-
-// Compute the default character width.
-function charWidth(display) {
- if (display.cachedCharWidth != null) { return display.cachedCharWidth }
- var anchor = elt("span", "xxxxxxxxxx")
- var pre = elt("pre", [anchor])
- removeChildrenAndAdd(display.measure, pre)
- var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10
- if (width > 2) { display.cachedCharWidth = width }
- return width || 10
-}
-
-// Do a bulk-read of the DOM positions and sizes needed to draw the
-// view, so that we don't interleave reading and writing to the DOM.
-function getDimensions(cm) {
- var d = cm.display, left = {}, width = {}
- var gutterLeft = d.gutters.clientLeft
- for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
- left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft
- width[cm.options.gutters[i]] = n.clientWidth
- }
- return {fixedPos: compensateForHScroll(d),
- gutterTotalWidth: d.gutters.offsetWidth,
- gutterLeft: left,
- gutterWidth: width,
- wrapperWidth: d.wrapper.clientWidth}
-}
-
-// Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
-// but using getBoundingClientRect to get a sub-pixel-accurate
-// result.
-function compensateForHScroll(display) {
- return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left
-}
-
-// Returns a function that estimates the height of a line, to use as
-// first approximation until the line becomes visible (and is thus
-// properly measurable).
-function estimateHeight(cm) {
- var th = textHeight(cm.display), wrapping = cm.options.lineWrapping
- var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3)
- return function (line) {
- if (lineIsHidden(cm.doc, line)) { return 0 }
-
- var widgetsHeight = 0
- if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {
- if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height }
- } }
-
- if (wrapping)
- { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }
- else
- { return widgetsHeight + th }
- }
-}
-
-function estimateLineHeights(cm) {
- var doc = cm.doc, est = estimateHeight(cm)
- doc.iter(function (line) {
- var estHeight = est(line)
- if (estHeight != line.height) { updateLineHeight(line, estHeight) }
- })
-}
-
-// Given a mouse event, find the corresponding position. If liberal
-// is false, it checks whether a gutter or scrollbar was clicked,
-// and returns null if it was. forRect is used by rectangular
-// selections, and tries to estimate a character position even for
-// coordinates beyond the right of the text.
-function posFromMouse(cm, e, liberal, forRect) {
- var display = cm.display
- if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null }
-
- var x, y, space = display.lineSpace.getBoundingClientRect()
- // Fails unpredictably on IE[67] when mouse is dragged around quickly.
- try { x = e.clientX - space.left; y = e.clientY - space.top }
- catch (e) { return null }
- var coords = coordsChar(cm, x, y), line
- if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
- var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length
- coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff))
- }
- return coords
-}
-
-// Find the view element corresponding to a given line. Return null
-// when the line isn't visible.
-function findViewIndex(cm, n) {
- if (n >= cm.display.viewTo) { return null }
- n -= cm.display.viewFrom
- if (n < 0) { return null }
- var view = cm.display.view
- for (var i = 0; i < view.length; i++) {
- n -= view[i].size
- if (n < 0) { return i }
- }
-}
-
-function updateSelection(cm) {
- cm.display.input.showSelection(cm.display.input.prepareSelection())
-}
-
-function prepareSelection(cm, primary) {
- var doc = cm.doc, result = {}
- var curFragment = result.cursors = document.createDocumentFragment()
- var selFragment = result.selection = document.createDocumentFragment()
-
- for (var i = 0; i < doc.sel.ranges.length; i++) {
- if (primary === false && i == doc.sel.primIndex) { continue }
- var range = doc.sel.ranges[i]
- if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue }
- var collapsed = range.empty()
- if (collapsed || cm.options.showCursorWhenSelecting)
- { drawSelectionCursor(cm, range.head, curFragment) }
- if (!collapsed)
- { drawSelectionRange(cm, range, selFragment) }
- }
- return result
-}
-
-// Draws a cursor for the given range
-function drawSelectionCursor(cm, head, output) {
- var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine)
-
- var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"))
- cursor.style.left = pos.left + "px"
- cursor.style.top = pos.top + "px"
- cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"
-
- if (pos.other) {
- // Secondary cursor, shown when on a 'jump' in bi-directional text
- var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"))
- otherCursor.style.display = ""
- otherCursor.style.left = pos.other.left + "px"
- otherCursor.style.top = pos.other.top + "px"
- otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"
- }
-}
-
-// Draws the given range as a highlighted selection
-function drawSelectionRange(cm, range, output) {
- var display = cm.display, doc = cm.doc
- var fragment = document.createDocumentFragment()
- var padding = paddingH(cm.display), leftSide = padding.left
- var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right
-
- function add(left, top, width, bottom) {
- if (top < 0) { top = 0 }
- top = Math.round(top)
- bottom = Math.round(bottom)
- fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px")))
- }
-
- function drawForLine(line, fromArg, toArg) {
- var lineObj = getLine(doc, line)
- var lineLen = lineObj.text.length
- var start, end
- function coords(ch, bias) {
- return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
- }
-
- iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) {
- var leftPos = coords(from, "left"), rightPos, left, right
- if (from == to) {
- rightPos = leftPos
- left = right = leftPos.left
- } else {
- rightPos = coords(to - 1, "right")
- if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp }
- left = leftPos.left
- right = rightPos.right
- }
- if (fromArg == null && from == 0) { left = leftSide }
- if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
- add(left, leftPos.top, null, leftPos.bottom)
- left = leftSide
- if (leftPos.bottom < rightPos.top) { add(left, leftPos.bottom, null, rightPos.top) }
- }
- if (toArg == null && to == lineLen) { right = rightSide }
- if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
- { start = leftPos }
- if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
- { end = rightPos }
- if (left < leftSide + 1) { left = leftSide }
- add(left, rightPos.top, right - left, rightPos.bottom)
- })
- return {start: start, end: end}
- }
-
- var sFrom = range.from(), sTo = range.to()
- if (sFrom.line == sTo.line) {
- drawForLine(sFrom.line, sFrom.ch, sTo.ch)
- } else {
- var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line)
- var singleVLine = visualLine(fromLine) == visualLine(toLine)
- var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end
- var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start
- if (singleVLine) {
- if (leftEnd.top < rightStart.top - 2) {
- add(leftEnd.right, leftEnd.top, null, leftEnd.bottom)
- add(leftSide, rightStart.top, rightStart.left, rightStart.bottom)
- } else {
- add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom)
- }
- }
- if (leftEnd.bottom < rightStart.top)
- { add(leftSide, leftEnd.bottom, null, rightStart.top) }
- }
-
- output.appendChild(fragment)
-}
-
-// Cursor-blinking
-function restartBlink(cm) {
- if (!cm.state.focused) { return }
- var display = cm.display
- clearInterval(display.blinker)
- var on = true
- display.cursorDiv.style.visibility = ""
- if (cm.options.cursorBlinkRate > 0)
- { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; },
- cm.options.cursorBlinkRate) }
- else if (cm.options.cursorBlinkRate < 0)
- { display.cursorDiv.style.visibility = "hidden" }
-}
-
-function ensureFocus(cm) {
- if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) }
-}
-
-function delayBlurEvent(cm) {
- cm.state.delayingBlurEvent = true
- setTimeout(function () { if (cm.state.delayingBlurEvent) {
- cm.state.delayingBlurEvent = false
- onBlur(cm)
- } }, 100)
-}
-
-function onFocus(cm, e) {
- if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false }
-
- if (cm.options.readOnly == "nocursor") { return }
- if (!cm.state.focused) {
- signal(cm, "focus", cm, e)
- cm.state.focused = true
- addClass(cm.display.wrapper, "CodeMirror-focused")
- // This test prevents this from firing when a context
- // menu is closed (since the input reset would kill the
- // select-all detection hack)
- if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
- cm.display.input.reset()
- if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730
- }
- cm.display.input.receivedFocus()
- }
- restartBlink(cm)
-}
-function onBlur(cm, e) {
- if (cm.state.delayingBlurEvent) { return }
-
- if (cm.state.focused) {
- signal(cm, "blur", cm, e)
- cm.state.focused = false
- rmClass(cm.display.wrapper, "CodeMirror-focused")
- }
- clearInterval(cm.display.blinker)
- setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150)
-}
-
-// Read the actual heights of the rendered lines, and update their
-// stored heights to match.
-function updateHeightsInViewport(cm) {
- var display = cm.display
- var prevBottom = display.lineDiv.offsetTop
- for (var i = 0; i < display.view.length; i++) {
- var cur = display.view[i], height = (void 0)
- if (cur.hidden) { continue }
- if (ie && ie_version < 8) {
- var bot = cur.node.offsetTop + cur.node.offsetHeight
- height = bot - prevBottom
- prevBottom = bot
- } else {
- var box = cur.node.getBoundingClientRect()
- height = box.bottom - box.top
- }
- var diff = cur.line.height - height
- if (height < 2) { height = textHeight(display) }
- if (diff > .005 || diff < -.005) {
- updateLineHeight(cur.line, height)
- updateWidgetHeight(cur.line)
- if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
- { updateWidgetHeight(cur.rest[j]) } }
- }
- }
-}
-
-// Read and store the height of line widgets associated with the
-// given line.
-function updateWidgetHeight(line) {
- if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i)
- { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } }
-}
-
-// Compute the lines that are visible in a given viewport (defaults
-// the the current scroll position). viewport may contain top,
-// height, and ensure (see op.scrollToPos) properties.
-function visibleLines(display, doc, viewport) {
- var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop
- top = Math.floor(top - paddingTop(display))
- var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight
-
- var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom)
- // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
- // forces those lines into the viewport (if possible).
- if (viewport && viewport.ensure) {
- var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line
- if (ensureFrom < from) {
- from = ensureFrom
- to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)
- } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
- from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight)
- to = ensureTo
- }
- }
- return {from: from, to: Math.max(to, from + 1)}
-}
-
-// Re-align line numbers and gutter marks to compensate for
-// horizontal scrolling.
-function alignHorizontally(cm) {
- var display = cm.display, view = display.view
- if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return }
- var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft
- var gutterW = display.gutters.offsetWidth, left = comp + "px"
- for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {
- if (cm.options.fixedGutter) {
- if (view[i].gutter)
- { view[i].gutter.style.left = left }
- if (view[i].gutterBackground)
- { view[i].gutterBackground.style.left = left }
- }
- var align = view[i].alignable
- if (align) { for (var j = 0; j < align.length; j++)
- { align[j].style.left = left } }
- } }
- if (cm.options.fixedGutter)
- { display.gutters.style.left = (comp + gutterW) + "px" }
-}
-
-// Used to ensure that the line number gutter is still the right
-// size for the current document size. Returns true when an update
-// is needed.
-function maybeUpdateLineNumberWidth(cm) {
- if (!cm.options.lineNumbers) { return false }
- var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display
- if (last.length != display.lineNumChars) {
- var test = display.measure.appendChild(elt("div", [elt("div", last)],
- "CodeMirror-linenumber CodeMirror-gutter-elt"))
- var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW
- display.lineGutter.style.width = ""
- display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1
- display.lineNumWidth = display.lineNumInnerWidth + padding
- display.lineNumChars = display.lineNumInnerWidth ? last.length : -1
- display.lineGutter.style.width = display.lineNumWidth + "px"
- updateGutterSpace(cm)
- return true
- }
- return false
-}
-
-// SCROLLING THINGS INTO VIEW
-
-// If an editor sits on the top or bottom of the window, partially
-// scrolled out of view, this ensures that the cursor is visible.
-function maybeScrollWindow(cm, rect) {
- if (signalDOMEvent(cm, "scrollCursorIntoView")) { return }
-
- var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null
- if (rect.top + box.top < 0) { doScroll = true }
- else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false }
- if (doScroll != null && !phantom) {
- var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;"))
- cm.display.lineSpace.appendChild(scrollNode)
- scrollNode.scrollIntoView(doScroll)
- cm.display.lineSpace.removeChild(scrollNode)
- }
-}
-
-// Scroll a given position into view (immediately), verifying that
-// it actually became visible (as line heights are accurately
-// measured, the position of something may 'drift' during drawing).
-function scrollPosIntoView(cm, pos, end, margin) {
- if (margin == null) { margin = 0 }
- var rect
- if (!cm.options.lineWrapping && pos == end) {
- // Set pos and end to the cursor positions around the character pos sticks to
- // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch
- // If pos == Pos(_, 0, "before"), pos and end are unchanged
- pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos
- end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos
- }
- for (var limit = 0; limit < 5; limit++) {
- var changed = false
- var coords = cursorCoords(cm, pos)
- var endCoords = !end || end == pos ? coords : cursorCoords(cm, end)
- rect = {left: Math.min(coords.left, endCoords.left),
- top: Math.min(coords.top, endCoords.top) - margin,
- right: Math.max(coords.left, endCoords.left),
- bottom: Math.max(coords.bottom, endCoords.bottom) + margin}
- var scrollPos = calculateScrollPos(cm, rect)
- var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft
- if (scrollPos.scrollTop != null) {
- updateScrollTop(cm, scrollPos.scrollTop)
- if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true }
- }
- if (scrollPos.scrollLeft != null) {
- setScrollLeft(cm, scrollPos.scrollLeft)
- if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true }
- }
- if (!changed) { break }
- }
- return rect
-}
-
-// Scroll a given set of coordinates into view (immediately).
-function scrollIntoView(cm, rect) {
- var scrollPos = calculateScrollPos(cm, rect)
- if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) }
- if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) }
-}
-
-// Calculate a new scroll position needed to scroll the given
-// rectangle into view. Returns an object with scrollTop and
-// scrollLeft properties. When these are undefined, the
-// vertical/horizontal position does not need to be adjusted.
-function calculateScrollPos(cm, rect) {
- var display = cm.display, snapMargin = textHeight(cm.display)
- if (rect.top < 0) { rect.top = 0 }
- var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop
- var screen = displayHeight(cm), result = {}
- if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen }
- var docBottom = cm.doc.height + paddingVert(display)
- var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin
- if (rect.top < screentop) {
- result.scrollTop = atTop ? 0 : rect.top
- } else if (rect.bottom > screentop + screen) {
- var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen)
- if (newTop != screentop) { result.scrollTop = newTop }
- }
-
- var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft
- var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0)
- var tooWide = rect.right - rect.left > screenw
- if (tooWide) { rect.right = rect.left + screenw }
- if (rect.left < 10)
- { result.scrollLeft = 0 }
- else if (rect.left < screenleft)
- { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) }
- else if (rect.right > screenw + screenleft - 3)
- { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw }
- return result
-}
-
-// Store a relative adjustment to the scroll position in the current
-// operation (to be applied when the operation finishes).
-function addToScrollTop(cm, top) {
- if (top == null) { return }
- resolveScrollToPos(cm)
- cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top
-}
-
-// Make sure that at the end of the operation the current cursor is
-// shown.
-function ensureCursorVisible(cm) {
- resolveScrollToPos(cm)
- var cur = cm.getCursor()
- cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}
-}
-
-function scrollToCoords(cm, x, y) {
- if (x != null || y != null) { resolveScrollToPos(cm) }
- if (x != null) { cm.curOp.scrollLeft = x }
- if (y != null) { cm.curOp.scrollTop = y }
-}
-
-function scrollToRange(cm, range) {
- resolveScrollToPos(cm)
- cm.curOp.scrollToPos = range
-}
-
-// When an operation has its scrollToPos property set, and another
-// scroll action is applied before the end of the operation, this
-// 'simulates' scrolling that position into view in a cheap way, so
-// that the effect of intermediate scroll commands is not ignored.
-function resolveScrollToPos(cm) {
- var range = cm.curOp.scrollToPos
- if (range) {
- cm.curOp.scrollToPos = null
- var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to)
- scrollToCoordsRange(cm, from, to, range.margin)
- }
-}
-
-function scrollToCoordsRange(cm, from, to, margin) {
- var sPos = calculateScrollPos(cm, {
- left: Math.min(from.left, to.left),
- top: Math.min(from.top, to.top) - margin,
- right: Math.max(from.right, to.right),
- bottom: Math.max(from.bottom, to.bottom) + margin
- })
- scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop)
-}
-
-// Sync the scrollable area and scrollbars, ensure the viewport
-// covers the visible area.
-function updateScrollTop(cm, val) {
- if (Math.abs(cm.doc.scrollTop - val) < 2) { return }
- if (!gecko) { updateDisplaySimple(cm, {top: val}) }
- setScrollTop(cm, val, true)
- if (gecko) { updateDisplaySimple(cm) }
- startWorker(cm, 100)
-}
-
-function setScrollTop(cm, val, forceScroll) {
- val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val)
- if (cm.display.scroller.scrollTop == val && !forceScroll) { return }
- cm.doc.scrollTop = val
- cm.display.scrollbars.setScrollTop(val)
- if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val }
-}
-
-// Sync scroller and scrollbar, ensure the gutter elements are
-// aligned.
-function setScrollLeft(cm, val, isScroller, forceScroll) {
- val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth)
- if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }
- cm.doc.scrollLeft = val
- alignHorizontally(cm)
- if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val }
- cm.display.scrollbars.setScrollLeft(val)
-}
-
-// SCROLLBARS
-
-// Prepare DOM reads needed to update the scrollbars. Done in one
-// shot to minimize update/measure roundtrips.
-function measureForScrollbars(cm) {
- var d = cm.display, gutterW = d.gutters.offsetWidth
- var docH = Math.round(cm.doc.height + paddingVert(cm.display))
- return {
- clientHeight: d.scroller.clientHeight,
- viewHeight: d.wrapper.clientHeight,
- scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
- viewWidth: d.wrapper.clientWidth,
- barLeft: cm.options.fixedGutter ? gutterW : 0,
- docHeight: docH,
- scrollHeight: docH + scrollGap(cm) + d.barHeight,
- nativeBarWidth: d.nativeBarWidth,
- gutterWidth: gutterW
- }
-}
-
-var NativeScrollbars = function(place, scroll, cm) {
- this.cm = cm
- var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar")
- var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar")
- place(vert); place(horiz)
-
- on(vert, "scroll", function () {
- if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") }
- })
- on(horiz, "scroll", function () {
- if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") }
- })
-
- this.checkedZeroWidth = false
- // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
- if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" }
-};
-
-NativeScrollbars.prototype.update = function (measure) {
- var needsH = measure.scrollWidth > measure.clientWidth + 1
- var needsV = measure.scrollHeight > measure.clientHeight + 1
- var sWidth = measure.nativeBarWidth
-
- if (needsV) {
- this.vert.style.display = "block"
- this.vert.style.bottom = needsH ? sWidth + "px" : "0"
- var totalHeight = measure.viewHeight - (needsH ? sWidth : 0)
- // A bug in IE8 can cause this value to be negative, so guard it.
- this.vert.firstChild.style.height =
- Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"
- } else {
- this.vert.style.display = ""
- this.vert.firstChild.style.height = "0"
- }
-
- if (needsH) {
- this.horiz.style.display = "block"
- this.horiz.style.right = needsV ? sWidth + "px" : "0"
- this.horiz.style.left = measure.barLeft + "px"
- var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
- this.horiz.firstChild.style.width =
- Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
- } else {
- this.horiz.style.display = ""
- this.horiz.firstChild.style.width = "0"
- }
-
- if (!this.checkedZeroWidth && measure.clientHeight > 0) {
- if (sWidth == 0) { this.zeroWidthHack() }
- this.checkedZeroWidth = true
- }
-
- return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
-};
-
-NativeScrollbars.prototype.setScrollLeft = function (pos) {
- if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos }
- if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") }
-};
-
-NativeScrollbars.prototype.setScrollTop = function (pos) {
- if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos }
- if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") }
-};
-
-NativeScrollbars.prototype.zeroWidthHack = function () {
- var w = mac && !mac_geMountainLion ? "12px" : "18px"
- this.horiz.style.height = this.vert.style.width = w
- this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"
- this.disableHoriz = new Delayed
- this.disableVert = new Delayed
-};
-
-NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {
- bar.style.pointerEvents = "auto"
- function maybeDisable() {
- // To find out whether the scrollbar is still visible, we
- // check whether the element under the pixel in the bottom
- // right corner of the scrollbar box is the scrollbar box
- // itself (when the bar is still visible) or its filler child
- // (when the bar is hidden). If it is still visible, we keep
- // it enabled, if it's hidden, we disable pointer events.
- var box = bar.getBoundingClientRect()
- var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)
- : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1)
- if (elt != bar) { bar.style.pointerEvents = "none" }
- else { delay.set(1000, maybeDisable) }
- }
- delay.set(1000, maybeDisable)
-};
-
-NativeScrollbars.prototype.clear = function () {
- var parent = this.horiz.parentNode
- parent.removeChild(this.horiz)
- parent.removeChild(this.vert)
-};
-
-var NullScrollbars = function () {};
-
-NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };
-NullScrollbars.prototype.setScrollLeft = function () {};
-NullScrollbars.prototype.setScrollTop = function () {};
-NullScrollbars.prototype.clear = function () {};
-
-function updateScrollbars(cm, measure) {
- if (!measure) { measure = measureForScrollbars(cm) }
- var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight
- updateScrollbarsInner(cm, measure)
- for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
- if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
- { updateHeightsInViewport(cm) }
- updateScrollbarsInner(cm, measureForScrollbars(cm))
- startWidth = cm.display.barWidth; startHeight = cm.display.barHeight
- }
-}
-
-// Re-synchronize the fake scrollbars with the actual size of the
-// content.
-function updateScrollbarsInner(cm, measure) {
- var d = cm.display
- var sizes = d.scrollbars.update(measure)
-
- d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"
- d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"
- d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
-
- if (sizes.right && sizes.bottom) {
- d.scrollbarFiller.style.display = "block"
- d.scrollbarFiller.style.height = sizes.bottom + "px"
- d.scrollbarFiller.style.width = sizes.right + "px"
- } else { d.scrollbarFiller.style.display = "" }
- if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
- d.gutterFiller.style.display = "block"
- d.gutterFiller.style.height = sizes.bottom + "px"
- d.gutterFiller.style.width = measure.gutterWidth + "px"
- } else { d.gutterFiller.style.display = "" }
-}
-
-var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}
-
-function initScrollbars(cm) {
- if (cm.display.scrollbars) {
- cm.display.scrollbars.clear()
- if (cm.display.scrollbars.addClass)
- { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) }
- }
-
- cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {
- cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller)
- // Prevent clicks in the scrollbars from killing focus
- on(node, "mousedown", function () {
- if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) }
- })
- node.setAttribute("cm-not-content", "true")
- }, function (pos, axis) {
- if (axis == "horizontal") { setScrollLeft(cm, pos) }
- else { updateScrollTop(cm, pos) }
- }, cm)
- if (cm.display.scrollbars.addClass)
- { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) }
-}
-
-// Operations are used to wrap a series of changes to the editor
-// state in such a way that each change won't have to update the
-// cursor and display (which would be awkward, slow, and
-// error-prone). Instead, display updates are batched and then all
-// combined and executed at once.
-
-var nextOpId = 0
-// Start a new operation.
-function startOperation(cm) {
- cm.curOp = {
- cm: cm,
- viewChanged: false, // Flag that indicates that lines might need to be redrawn
- startHeight: cm.doc.height, // Used to detect need to update scrollbar
- forceUpdate: false, // Used to force a redraw
- updateInput: null, // Whether to reset the input textarea
- typing: false, // Whether this reset should be careful to leave existing text (for compositing)
- changeObjs: null, // Accumulated changes, for firing change events
- cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
- cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
- selectionChanged: false, // Whether the selection needs to be redrawn
- updateMaxLine: false, // Set when the widest line needs to be determined anew
- scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
- scrollToPos: null, // Used to scroll to a specific position
- focus: false,
- id: ++nextOpId // Unique ID
- }
- pushOperation(cm.curOp)
-}
-
-// Finish an operation, updating the display and signalling delayed events
-function endOperation(cm) {
- var op = cm.curOp
- finishOperation(op, function (group) {
- for (var i = 0; i < group.ops.length; i++)
- { group.ops[i].cm.curOp = null }
- endOperations(group)
- })
-}
-
-// The DOM updates done when an operation finishes are batched so
-// that the minimum number of relayouts are required.
-function endOperations(group) {
- var ops = group.ops
- for (var i = 0; i < ops.length; i++) // Read DOM
- { endOperation_R1(ops[i]) }
- for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)
- { endOperation_W1(ops[i$1]) }
- for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM
- { endOperation_R2(ops[i$2]) }
- for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)
- { endOperation_W2(ops[i$3]) }
- for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM
- { endOperation_finish(ops[i$4]) }
-}
-
-function endOperation_R1(op) {
- var cm = op.cm, display = cm.display
- maybeClipScrollbars(cm)
- if (op.updateMaxLine) { findMaxLine(cm) }
-
- op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
- op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
- op.scrollToPos.to.line >= display.viewTo) ||
- display.maxLineChanged && cm.options.lineWrapping
- op.update = op.mustUpdate &&
- new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate)
-}
-
-function endOperation_W1(op) {
- op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update)
-}
-
-function endOperation_R2(op) {
- var cm = op.cm, display = cm.display
- if (op.updatedDisplay) { updateHeightsInViewport(cm) }
-
- op.barMeasure = measureForScrollbars(cm)
-
- // If the max line changed since it was last measured, measure it,
- // and ensure the document's width matches it.
- // updateDisplay_W2 will use these properties to do the actual resizing
- if (display.maxLineChanged && !cm.options.lineWrapping) {
- op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3
- cm.display.sizerWidth = op.adjustWidthTo
- op.barMeasure.scrollWidth =
- Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth)
- op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
- }
-
- if (op.updatedDisplay || op.selectionChanged)
- { op.preparedSelection = display.input.prepareSelection(op.focus) }
-}
-
-function endOperation_W2(op) {
- var cm = op.cm
-
- if (op.adjustWidthTo != null) {
- cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"
- if (op.maxScrollLeft < cm.doc.scrollLeft)
- { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) }
- cm.display.maxLineChanged = false
- }
-
- var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
- if (op.preparedSelection)
- { cm.display.input.showSelection(op.preparedSelection, takeFocus) }
- if (op.updatedDisplay || op.startHeight != cm.doc.height)
- { updateScrollbars(cm, op.barMeasure) }
- if (op.updatedDisplay)
- { setDocumentHeight(cm, op.barMeasure) }
-
- if (op.selectionChanged) { restartBlink(cm) }
-
- if (cm.state.focused && op.updateInput)
- { cm.display.input.reset(op.typing) }
- if (takeFocus) { ensureFocus(op.cm) }
-}
-
-function endOperation_finish(op) {
- var cm = op.cm, display = cm.display, doc = cm.doc
-
- if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) }
-
- // Abort mouse wheel delta measurement, when scrolling explicitly
- if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
- { display.wheelStartX = display.wheelStartY = null }
-
- // Propagate the scroll position to the actual DOM scroller
- if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll) }
-
- if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true) }
- // If we need to scroll a specific position into view, do so.
- if (op.scrollToPos) {
- var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
- clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin)
- maybeScrollWindow(cm, rect)
- }
-
- // Fire events for markers that are hidden/unidden by editing or
- // undoing
- var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers
- if (hidden) { for (var i = 0; i < hidden.length; ++i)
- { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } }
- if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)
- { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } }
-
- if (display.wrapper.offsetHeight)
- { doc.scrollTop = cm.display.scroller.scrollTop }
-
- // Fire change events, and delayed event handlers
- if (op.changeObjs)
- { signal(cm, "changes", cm, op.changeObjs) }
- if (op.update)
- { op.update.finish() }
-}
-
-// Run the given function in an operation
-function runInOp(cm, f) {
- if (cm.curOp) { return f() }
- startOperation(cm)
- try { return f() }
- finally { endOperation(cm) }
-}
-// Wraps a function in an operation. Returns the wrapped function.
-function operation(cm, f) {
- return function() {
- if (cm.curOp) { return f.apply(cm, arguments) }
- startOperation(cm)
- try { return f.apply(cm, arguments) }
- finally { endOperation(cm) }
- }
-}
-// Used to add methods to editor and doc instances, wrapping them in
-// operations.
-function methodOp(f) {
- return function() {
- if (this.curOp) { return f.apply(this, arguments) }
- startOperation(this)
- try { return f.apply(this, arguments) }
- finally { endOperation(this) }
- }
-}
-function docMethodOp(f) {
- return function() {
- var cm = this.cm
- if (!cm || cm.curOp) { return f.apply(this, arguments) }
- startOperation(cm)
- try { return f.apply(this, arguments) }
- finally { endOperation(cm) }
- }
-}
-
-// Updates the display.view data structure for a given change to the
-// document. From and to are in pre-change coordinates. Lendiff is
-// the amount of lines added or subtracted by the change. This is
-// used for changes that span multiple lines, or change the way
-// lines are divided into visual lines. regLineChange (below)
-// registers single-line changes.
-function regChange(cm, from, to, lendiff) {
- if (from == null) { from = cm.doc.first }
- if (to == null) { to = cm.doc.first + cm.doc.size }
- if (!lendiff) { lendiff = 0 }
-
- var display = cm.display
- if (lendiff && to < display.viewTo &&
- (display.updateLineNumbers == null || display.updateLineNumbers > from))
- { display.updateLineNumbers = from }
-
- cm.curOp.viewChanged = true
-
- if (from >= display.viewTo) { // Change after
- if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
- { resetView(cm) }
- } else if (to <= display.viewFrom) { // Change before
- if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
- resetView(cm)
- } else {
- display.viewFrom += lendiff
- display.viewTo += lendiff
- }
- } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
- resetView(cm)
- } else if (from <= display.viewFrom) { // Top overlap
- var cut = viewCuttingPoint(cm, to, to + lendiff, 1)
- if (cut) {
- display.view = display.view.slice(cut.index)
- display.viewFrom = cut.lineN
- display.viewTo += lendiff
- } else {
- resetView(cm)
- }
- } else if (to >= display.viewTo) { // Bottom overlap
- var cut$1 = viewCuttingPoint(cm, from, from, -1)
- if (cut$1) {
- display.view = display.view.slice(0, cut$1.index)
- display.viewTo = cut$1.lineN
- } else {
- resetView(cm)
- }
- } else { // Gap in the middle
- var cutTop = viewCuttingPoint(cm, from, from, -1)
- var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1)
- if (cutTop && cutBot) {
- display.view = display.view.slice(0, cutTop.index)
- .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
- .concat(display.view.slice(cutBot.index))
- display.viewTo += lendiff
- } else {
- resetView(cm)
- }
- }
-
- var ext = display.externalMeasured
- if (ext) {
- if (to < ext.lineN)
- { ext.lineN += lendiff }
- else if (from < ext.lineN + ext.size)
- { display.externalMeasured = null }
- }
-}
-
-// Register a change to a single line. Type must be one of "text",
-// "gutter", "class", "widget"
-function regLineChange(cm, line, type) {
- cm.curOp.viewChanged = true
- var display = cm.display, ext = cm.display.externalMeasured
- if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
- { display.externalMeasured = null }
-
- if (line < display.viewFrom || line >= display.viewTo) { return }
- var lineView = display.view[findViewIndex(cm, line)]
- if (lineView.node == null) { return }
- var arr = lineView.changes || (lineView.changes = [])
- if (indexOf(arr, type) == -1) { arr.push(type) }
-}
-
-// Clear the view.
-function resetView(cm) {
- cm.display.viewFrom = cm.display.viewTo = cm.doc.first
- cm.display.view = []
- cm.display.viewOffset = 0
-}
-
-function viewCuttingPoint(cm, oldN, newN, dir) {
- var index = findViewIndex(cm, oldN), diff, view = cm.display.view
- if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
- { return {index: index, lineN: newN} }
- var n = cm.display.viewFrom
- for (var i = 0; i < index; i++)
- { n += view[i].size }
- if (n != oldN) {
- if (dir > 0) {
- if (index == view.length - 1) { return null }
- diff = (n + view[index].size) - oldN
- index++
- } else {
- diff = n - oldN
- }
- oldN += diff; newN += diff
- }
- while (visualLineNo(cm.doc, newN) != newN) {
- if (index == (dir < 0 ? 0 : view.length - 1)) { return null }
- newN += dir * view[index - (dir < 0 ? 1 : 0)].size
- index += dir
- }
- return {index: index, lineN: newN}
-}
-
-// Force the view to cover a given range, adding empty view element
-// or clipping off existing ones as needed.
-function adjustView(cm, from, to) {
- var display = cm.display, view = display.view
- if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
- display.view = buildViewArray(cm, from, to)
- display.viewFrom = from
- } else {
- if (display.viewFrom > from)
- { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) }
- else if (display.viewFrom < from)
- { display.view = display.view.slice(findViewIndex(cm, from)) }
- display.viewFrom = from
- if (display.viewTo < to)
- { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) }
- else if (display.viewTo > to)
- { display.view = display.view.slice(0, findViewIndex(cm, to)) }
- }
- display.viewTo = to
-}
-
-// Count the number of lines in the view whose DOM representation is
-// out of date (or nonexistent).
-function countDirtyView(cm) {
- var view = cm.display.view, dirty = 0
- for (var i = 0; i < view.length; i++) {
- var lineView = view[i]
- if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty }
- }
- return dirty
-}
-
-// HIGHLIGHT WORKER
-
-function startWorker(cm, time) {
- if (cm.doc.highlightFrontier < cm.display.viewTo)
- { cm.state.highlight.set(time, bind(highlightWorker, cm)) }
-}
-
-function highlightWorker(cm) {
- var doc = cm.doc
- if (doc.highlightFrontier >= cm.display.viewTo) { return }
- var end = +new Date + cm.options.workTime
- var context = getContextBefore(cm, doc.highlightFrontier)
- var changedLines = []
-
- doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {
- if (context.line >= cm.display.viewFrom) { // Visible
- var oldStyles = line.styles
- var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null
- var highlighted = highlightLine(cm, line, context, true)
- if (resetState) { context.state = resetState }
- line.styles = highlighted.styles
- var oldCls = line.styleClasses, newCls = highlighted.classes
- if (newCls) { line.styleClasses = newCls }
- else if (oldCls) { line.styleClasses = null }
- var ischange = !oldStyles || oldStyles.length != line.styles.length ||
- oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass)
- for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] }
- if (ischange) { changedLines.push(context.line) }
- line.stateAfter = context.save()
- context.nextLine()
- } else {
- if (line.text.length <= cm.options.maxHighlightLength)
- { processLine(cm, line.text, context) }
- line.stateAfter = context.line % 5 == 0 ? context.save() : null
- context.nextLine()
- }
- if (+new Date > end) {
- startWorker(cm, cm.options.workDelay)
- return true
- }
- })
- doc.highlightFrontier = context.line
- doc.modeFrontier = Math.max(doc.modeFrontier, context.line)
- if (changedLines.length) { runInOp(cm, function () {
- for (var i = 0; i < changedLines.length; i++)
- { regLineChange(cm, changedLines[i], "text") }
- }) }
-}
-
-// DISPLAY DRAWING
-
-var DisplayUpdate = function(cm, viewport, force) {
- var display = cm.display
-
- this.viewport = viewport
- // Store some values that we'll need later (but don't want to force a relayout for)
- this.visible = visibleLines(display, cm.doc, viewport)
- this.editorIsHidden = !display.wrapper.offsetWidth
- this.wrapperHeight = display.wrapper.clientHeight
- this.wrapperWidth = display.wrapper.clientWidth
- this.oldDisplayWidth = displayWidth(cm)
- this.force = force
- this.dims = getDimensions(cm)
- this.events = []
-};
-
-DisplayUpdate.prototype.signal = function (emitter, type) {
- if (hasHandler(emitter, type))
- { this.events.push(arguments) }
-};
-DisplayUpdate.prototype.finish = function () {
- var this$1 = this;
-
- for (var i = 0; i < this.events.length; i++)
- { signal.apply(null, this$1.events[i]) }
-};
-
-function maybeClipScrollbars(cm) {
- var display = cm.display
- if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
- display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
- display.heightForcer.style.height = scrollGap(cm) + "px"
- display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
- display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
- display.scrollbarsClipped = true
- }
-}
-
-function selectionSnapshot(cm) {
- if (cm.hasFocus()) { return null }
- var active = activeElt()
- if (!active || !contains(cm.display.lineDiv, active)) { return null }
- var result = {activeElt: active}
- if (window.getSelection) {
- var sel = window.getSelection()
- if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
- result.anchorNode = sel.anchorNode
- result.anchorOffset = sel.anchorOffset
- result.focusNode = sel.focusNode
- result.focusOffset = sel.focusOffset
- }
- }
- return result
-}
-
-function restoreSelection(snapshot) {
- if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }
- snapshot.activeElt.focus()
- if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
- var sel = window.getSelection(), range = document.createRange()
- range.setEnd(snapshot.anchorNode, snapshot.anchorOffset)
- range.collapse(false)
- sel.removeAllRanges()
- sel.addRange(range)
- sel.extend(snapshot.focusNode, snapshot.focusOffset)
- }
-}
-
-// Does the actual updating of the line display. Bails out
-// (returning false) when there is nothing to be done and forced is
-// false.
-function updateDisplayIfNeeded(cm, update) {
- var display = cm.display, doc = cm.doc
-
- if (update.editorIsHidden) {
- resetView(cm)
- return false
- }
-
- // Bail out if the visible area is already rendered and nothing changed.
- if (!update.force &&
- update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
- (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
- display.renderedView == display.view && countDirtyView(cm) == 0)
- { return false }
-
- if (maybeUpdateLineNumberWidth(cm)) {
- resetView(cm)
- update.dims = getDimensions(cm)
- }
-
- // Compute a suitable new viewport (from & to)
- var end = doc.first + doc.size
- var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first)
- var to = Math.min(end, update.visible.to + cm.options.viewportMargin)
- if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) }
- if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) }
- if (sawCollapsedSpans) {
- from = visualLineNo(cm.doc, from)
- to = visualLineEndNo(cm.doc, to)
- }
-
- var different = from != display.viewFrom || to != display.viewTo ||
- display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth
- adjustView(cm, from, to)
-
- display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom))
- // Position the mover div to align with the current scroll position
- cm.display.mover.style.top = display.viewOffset + "px"
-
- var toUpdate = countDirtyView(cm)
- if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
- (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
- { return false }
-
- // For big changes, we hide the enclosing element during the
- // update, since that speeds up the operations on most browsers.
- var selSnapshot = selectionSnapshot(cm)
- if (toUpdate > 4) { display.lineDiv.style.display = "none" }
- patchDisplay(cm, display.updateLineNumbers, update.dims)
- if (toUpdate > 4) { display.lineDiv.style.display = "" }
- display.renderedView = display.view
- // There might have been a widget with a focused element that got
- // hidden or updated, if so re-focus it.
- restoreSelection(selSnapshot)
-
- // Prevent selection and cursors from interfering with the scroll
- // width and height.
- removeChildren(display.cursorDiv)
- removeChildren(display.selectionDiv)
- display.gutters.style.height = display.sizer.style.minHeight = 0
-
- if (different) {
- display.lastWrapHeight = update.wrapperHeight
- display.lastWrapWidth = update.wrapperWidth
- startWorker(cm, 400)
- }
-
- display.updateLineNumbers = null
-
- return true
-}
-
-function postUpdateDisplay(cm, update) {
- var viewport = update.viewport
-
- for (var first = true;; first = false) {
- if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
- // Clip forced viewport to actual scrollable area.
- if (viewport && viewport.top != null)
- { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} }
- // Updated line heights might result in the drawn area not
- // actually covering the viewport. Keep looping until it does.
- update.visible = visibleLines(cm.display, cm.doc, viewport)
- if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
- { break }
- }
- if (!updateDisplayIfNeeded(cm, update)) { break }
- updateHeightsInViewport(cm)
- var barMeasure = measureForScrollbars(cm)
- updateSelection(cm)
- updateScrollbars(cm, barMeasure)
- setDocumentHeight(cm, barMeasure)
- update.force = false
- }
-
- update.signal(cm, "update", cm)
- if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
- update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo)
- cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo
- }
-}
-
-function updateDisplaySimple(cm, viewport) {
- var update = new DisplayUpdate(cm, viewport)
- if (updateDisplayIfNeeded(cm, update)) {
- updateHeightsInViewport(cm)
- postUpdateDisplay(cm, update)
- var barMeasure = measureForScrollbars(cm)
- updateSelection(cm)
- updateScrollbars(cm, barMeasure)
- setDocumentHeight(cm, barMeasure)
- update.finish()
- }
-}
-
-// Sync the actual display DOM structure with display.view, removing
-// nodes for lines that are no longer in view, and creating the ones
-// that are not there yet, and updating the ones that are out of
-// date.
-function patchDisplay(cm, updateNumbersFrom, dims) {
- var display = cm.display, lineNumbers = cm.options.lineNumbers
- var container = display.lineDiv, cur = container.firstChild
-
- function rm(node) {
- var next = node.nextSibling
- // Works around a throw-scroll bug in OS X Webkit
- if (webkit && mac && cm.display.currentWheelTarget == node)
- { node.style.display = "none" }
- else
- { node.parentNode.removeChild(node) }
- return next
- }
-
- var view = display.view, lineN = display.viewFrom
- // Loop over the elements in the view, syncing cur (the DOM nodes
- // in display.lineDiv) with the view as we go.
- for (var i = 0; i < view.length; i++) {
- var lineView = view[i]
- if (lineView.hidden) {
- } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
- var node = buildLineElement(cm, lineView, lineN, dims)
- container.insertBefore(node, cur)
- } else { // Already drawn
- while (cur != lineView.node) { cur = rm(cur) }
- var updateNumber = lineNumbers && updateNumbersFrom != null &&
- updateNumbersFrom <= lineN && lineView.lineNumber
- if (lineView.changes) {
- if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false }
- updateLineForChanges(cm, lineView, lineN, dims)
- }
- if (updateNumber) {
- removeChildren(lineView.lineNumber)
- lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)))
- }
- cur = lineView.node.nextSibling
- }
- lineN += lineView.size
- }
- while (cur) { cur = rm(cur) }
-}
-
-function updateGutterSpace(cm) {
- var width = cm.display.gutters.offsetWidth
- cm.display.sizer.style.marginLeft = width + "px"
-}
-
-function setDocumentHeight(cm, measure) {
- cm.display.sizer.style.minHeight = measure.docHeight + "px"
- cm.display.heightForcer.style.top = measure.docHeight + "px"
- cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"
-}
-
-// Rebuild the gutter elements, ensure the margin to the left of the
-// code matches their width.
-function updateGutters(cm) {
- var gutters = cm.display.gutters, specs = cm.options.gutters
- removeChildren(gutters)
- var i = 0
- for (; i < specs.length; ++i) {
- var gutterClass = specs[i]
- var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass))
- if (gutterClass == "CodeMirror-linenumbers") {
- cm.display.lineGutter = gElt
- gElt.style.width = (cm.display.lineNumWidth || 1) + "px"
- }
- }
- gutters.style.display = i ? "" : "none"
- updateGutterSpace(cm)
-}
-
-// Make sure the gutters options contains the element
-// "CodeMirror-linenumbers" when the lineNumbers option is true.
-function setGuttersForLineNumbers(options) {
- var found = indexOf(options.gutters, "CodeMirror-linenumbers")
- if (found == -1 && options.lineNumbers) {
- options.gutters = options.gutters.concat(["CodeMirror-linenumbers"])
- } else if (found > -1 && !options.lineNumbers) {
- options.gutters = options.gutters.slice(0)
- options.gutters.splice(found, 1)
- }
-}
-
-var wheelSamples = 0;
-var wheelPixelsPerUnit = null;
-// Fill in a browser-detected starting value on browsers where we
-// know one. These don't have to be accurate -- the result of them
-// being wrong would just be a slight flicker on the first wheel
-// scroll (if it is large enough).
-if (ie) { wheelPixelsPerUnit = -.53 }
-else if (gecko) { wheelPixelsPerUnit = 15 }
-else if (chrome) { wheelPixelsPerUnit = -.7 }
-else if (safari) { wheelPixelsPerUnit = -1/3 }
-
-function wheelEventDelta(e) {
- var dx = e.wheelDeltaX, dy = e.wheelDeltaY
- if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail }
- if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail }
- else if (dy == null) { dy = e.wheelDelta }
- return {x: dx, y: dy}
-}
-function wheelEventPixels(e) {
- var delta = wheelEventDelta(e)
- delta.x *= wheelPixelsPerUnit
- delta.y *= wheelPixelsPerUnit
- return delta
-}
-
-function onScrollWheel(cm, e) {
- var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y
-
- var display = cm.display, scroll = display.scroller
- // Quit if there's nothing to scroll here
- var canScrollX = scroll.scrollWidth > scroll.clientWidth
- var canScrollY = scroll.scrollHeight > scroll.clientHeight
- if (!(dx && canScrollX || dy && canScrollY)) { return }
-
- // Webkit browsers on OS X abort momentum scrolls when the target
- // of the scroll event is removed from the scrollable element.
- // This hack (see related code in patchDisplay) makes sure the
- // element is kept around.
- if (dy && mac && webkit) {
- outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
- for (var i = 0; i < view.length; i++) {
- if (view[i].node == cur) {
- cm.display.currentWheelTarget = cur
- break outer
- }
- }
- }
- }
-
- // On some browsers, horizontal scrolling will cause redraws to
- // happen before the gutter has been realigned, causing it to
- // wriggle around in a most unseemly way. When we have an
- // estimated pixels/delta value, we just handle horizontal
- // scrolling entirely here. It'll be slightly off from native, but
- // better than glitching out.
- if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
- if (dy && canScrollY)
- { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) }
- setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit))
- // Only prevent default scrolling if vertical scrolling is
- // actually possible. Otherwise, it causes vertical scroll
- // jitter on OSX trackpads when deltaX is small and deltaY
- // is large (issue #3579)
- if (!dy || (dy && canScrollY))
- { e_preventDefault(e) }
- display.wheelStartX = null // Abort measurement, if in progress
- return
- }
-
- // 'Project' the visible viewport to cover the area that is being
- // scrolled into view (if we know enough to estimate it).
- if (dy && wheelPixelsPerUnit != null) {
- var pixels = dy * wheelPixelsPerUnit
- var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight
- if (pixels < 0) { top = Math.max(0, top + pixels - 50) }
- else { bot = Math.min(cm.doc.height, bot + pixels + 50) }
- updateDisplaySimple(cm, {top: top, bottom: bot})
- }
-
- if (wheelSamples < 20) {
- if (display.wheelStartX == null) {
- display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop
- display.wheelDX = dx; display.wheelDY = dy
- setTimeout(function () {
- if (display.wheelStartX == null) { return }
- var movedX = scroll.scrollLeft - display.wheelStartX
- var movedY = scroll.scrollTop - display.wheelStartY
- var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
- (movedX && display.wheelDX && movedX / display.wheelDX)
- display.wheelStartX = display.wheelStartY = null
- if (!sample) { return }
- wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1)
- ++wheelSamples
- }, 200)
- } else {
- display.wheelDX += dx; display.wheelDY += dy
- }
- }
-}
-
-// Selection objects are immutable. A new one is created every time
-// the selection changes. A selection is one or more non-overlapping
-// (and non-touching) ranges, sorted, and an integer that indicates
-// which one is the primary selection (the one that's scrolled into
-// view, that getCursor returns, etc).
-var Selection = function(ranges, primIndex) {
- this.ranges = ranges
- this.primIndex = primIndex
-};
-
-Selection.prototype.primary = function () { return this.ranges[this.primIndex] };
-
-Selection.prototype.equals = function (other) {
- var this$1 = this;
-
- if (other == this) { return true }
- if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }
- for (var i = 0; i < this.ranges.length; i++) {
- var here = this$1.ranges[i], there = other.ranges[i]
- if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }
- }
- return true
-};
-
-Selection.prototype.deepCopy = function () {
- var this$1 = this;
-
- var out = []
- for (var i = 0; i < this.ranges.length; i++)
- { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) }
- return new Selection(out, this.primIndex)
-};
-
-Selection.prototype.somethingSelected = function () {
- var this$1 = this;
-
- for (var i = 0; i < this.ranges.length; i++)
- { if (!this$1.ranges[i].empty()) { return true } }
- return false
-};
-
-Selection.prototype.contains = function (pos, end) {
- var this$1 = this;
-
- if (!end) { end = pos }
- for (var i = 0; i < this.ranges.length; i++) {
- var range = this$1.ranges[i]
- if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
- { return i }
- }
- return -1
-};
-
-var Range = function(anchor, head) {
- this.anchor = anchor; this.head = head
-};
-
-Range.prototype.from = function () { return minPos(this.anchor, this.head) };
-Range.prototype.to = function () { return maxPos(this.anchor, this.head) };
-Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };
-
-// Take an unsorted, potentially overlapping set of ranges, and
-// build a selection out of it. 'Consumes' ranges array (modifying
-// it).
-function normalizeSelection(ranges, primIndex) {
- var prim = ranges[primIndex]
- ranges.sort(function (a, b) { return cmp(a.from(), b.from()); })
- primIndex = indexOf(ranges, prim)
- for (var i = 1; i < ranges.length; i++) {
- var cur = ranges[i], prev = ranges[i - 1]
- if (cmp(prev.to(), cur.from()) >= 0) {
- var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to())
- var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head
- if (i <= primIndex) { --primIndex }
- ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to))
- }
- }
- return new Selection(ranges, primIndex)
-}
-
-function simpleSelection(anchor, head) {
- return new Selection([new Range(anchor, head || anchor)], 0)
-}
-
-// Compute the position of the end of a change (its 'to' property
-// refers to the pre-change end).
-function changeEnd(change) {
- if (!change.text) { return change.to }
- return Pos(change.from.line + change.text.length - 1,
- lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))
-}
-
-// Adjust a position to refer to the post-change position of the
-// same text, or the end of the change if the change covers it.
-function adjustForChange(pos, change) {
- if (cmp(pos, change.from) < 0) { return pos }
- if (cmp(pos, change.to) <= 0) { return changeEnd(change) }
-
- var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch
- if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch }
- return Pos(line, ch)
-}
-
-function computeSelAfterChange(doc, change) {
- var out = []
- for (var i = 0; i < doc.sel.ranges.length; i++) {
- var range = doc.sel.ranges[i]
- out.push(new Range(adjustForChange(range.anchor, change),
- adjustForChange(range.head, change)))
- }
- return normalizeSelection(out, doc.sel.primIndex)
-}
-
-function offsetPos(pos, old, nw) {
- if (pos.line == old.line)
- { return Pos(nw.line, pos.ch - old.ch + nw.ch) }
- else
- { return Pos(nw.line + (pos.line - old.line), pos.ch) }
-}
-
-// Used by replaceSelections to allow moving the selection to the
-// start or around the replaced test. Hint may be "start" or "around".
-function computeReplacedSel(doc, changes, hint) {
- var out = []
- var oldPrev = Pos(doc.first, 0), newPrev = oldPrev
- for (var i = 0; i < changes.length; i++) {
- var change = changes[i]
- var from = offsetPos(change.from, oldPrev, newPrev)
- var to = offsetPos(changeEnd(change), oldPrev, newPrev)
- oldPrev = change.to
- newPrev = to
- if (hint == "around") {
- var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0
- out[i] = new Range(inv ? to : from, inv ? from : to)
- } else {
- out[i] = new Range(from, from)
- }
- }
- return new Selection(out, doc.sel.primIndex)
-}
-
-// Used to get the editor into a consistent state again when options change.
-
-function loadMode(cm) {
- cm.doc.mode = getMode(cm.options, cm.doc.modeOption)
- resetModeState(cm)
-}
-
-function resetModeState(cm) {
- cm.doc.iter(function (line) {
- if (line.stateAfter) { line.stateAfter = null }
- if (line.styles) { line.styles = null }
- })
- cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first
- startWorker(cm, 100)
- cm.state.modeGen++
- if (cm.curOp) { regChange(cm) }
-}
-
-// DOCUMENT DATA STRUCTURE
-
-// By default, updates that start and end at the beginning of a line
-// are treated specially, in order to make the association of line
-// widgets and marker elements with the text behave more intuitive.
-function isWholeLineUpdate(doc, change) {
- return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
- (!doc.cm || doc.cm.options.wholeLineUpdateBefore)
-}
-
-// Perform a change on the document data structure.
-function updateDoc(doc, change, markedSpans, estimateHeight) {
- function spansFor(n) {return markedSpans ? markedSpans[n] : null}
- function update(line, text, spans) {
- updateLine(line, text, spans, estimateHeight)
- signalLater(line, "change", line, change)
- }
- function linesFor(start, end) {
- var result = []
- for (var i = start; i < end; ++i)
- { result.push(new Line(text[i], spansFor(i), estimateHeight)) }
- return result
- }
-
- var from = change.from, to = change.to, text = change.text
- var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line)
- var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line
-
- // Adjust the line structure
- if (change.full) {
- doc.insert(0, linesFor(0, text.length))
- doc.remove(text.length, doc.size - text.length)
- } else if (isWholeLineUpdate(doc, change)) {
- // This is a whole-line replace. Treated specially to make
- // sure line objects move the way they are supposed to.
- var added = linesFor(0, text.length - 1)
- update(lastLine, lastLine.text, lastSpans)
- if (nlines) { doc.remove(from.line, nlines) }
- if (added.length) { doc.insert(from.line, added) }
- } else if (firstLine == lastLine) {
- if (text.length == 1) {
- update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans)
- } else {
- var added$1 = linesFor(1, text.length - 1)
- added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight))
- update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
- doc.insert(from.line + 1, added$1)
- }
- } else if (text.length == 1) {
- update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0))
- doc.remove(from.line + 1, nlines)
- } else {
- update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
- update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans)
- var added$2 = linesFor(1, text.length - 1)
- if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) }
- doc.insert(from.line + 1, added$2)
- }
-
- signalLater(doc, "change", doc, change)
-}
-
-// Call f for all linked documents.
-function linkedDocs(doc, f, sharedHistOnly) {
- function propagate(doc, skip, sharedHist) {
- if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {
- var rel = doc.linked[i]
- if (rel.doc == skip) { continue }
- var shared = sharedHist && rel.sharedHist
- if (sharedHistOnly && !shared) { continue }
- f(rel.doc, shared)
- propagate(rel.doc, doc, shared)
- } }
- }
- propagate(doc, null, true)
-}
-
-// Attach a document to an editor.
-function attachDoc(cm, doc) {
- if (doc.cm) { throw new Error("This document is already in use.") }
- cm.doc = doc
- doc.cm = cm
- estimateLineHeights(cm)
- loadMode(cm)
- setDirectionClass(cm)
- if (!cm.options.lineWrapping) { findMaxLine(cm) }
- cm.options.mode = doc.modeOption
- regChange(cm)
-}
-
-function setDirectionClass(cm) {
- ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl")
-}
-
-function directionChanged(cm) {
- runInOp(cm, function () {
- setDirectionClass(cm)
- regChange(cm)
- })
-}
-
-function History(startGen) {
- // Arrays of change events and selections. Doing something adds an
- // event to done and clears undo. Undoing moves events from done
- // to undone, redoing moves them in the other direction.
- this.done = []; this.undone = []
- this.undoDepth = Infinity
- // Used to track when changes can be merged into a single undo
- // event
- this.lastModTime = this.lastSelTime = 0
- this.lastOp = this.lastSelOp = null
- this.lastOrigin = this.lastSelOrigin = null
- // Used by the isClean() method
- this.generation = this.maxGeneration = startGen || 1
-}
-
-// Create a history change event from an updateDoc-style change
-// object.
-function historyChangeFromChange(doc, change) {
- var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}
- attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1)
- linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true)
- return histChange
-}
-
-// Pop all selection events off the end of a history array. Stop at
-// a change event.
-function clearSelectionEvents(array) {
- while (array.length) {
- var last = lst(array)
- if (last.ranges) { array.pop() }
- else { break }
- }
-}
-
-// Find the top change event in the history. Pop off selection
-// events that are in the way.
-function lastChangeEvent(hist, force) {
- if (force) {
- clearSelectionEvents(hist.done)
- return lst(hist.done)
- } else if (hist.done.length && !lst(hist.done).ranges) {
- return lst(hist.done)
- } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
- hist.done.pop()
- return lst(hist.done)
- }
-}
-
-// Register a change in the history. Merges changes that are within
-// a single operation, or are close together with an origin that
-// allows merging (starting with "+") into a single event.
-function addChangeToHistory(doc, change, selAfter, opId) {
- var hist = doc.history
- hist.undone.length = 0
- var time = +new Date, cur
- var last
-
- if ((hist.lastOp == opId ||
- hist.lastOrigin == change.origin && change.origin &&
- ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
- change.origin.charAt(0) == "*")) &&
- (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
- // Merge this change into the last event
- last = lst(cur.changes)
- if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
- // Optimized case for simple insertion -- don't want to add
- // new changesets for every character typed
- last.to = changeEnd(change)
- } else {
- // Add new sub-event
- cur.changes.push(historyChangeFromChange(doc, change))
- }
- } else {
- // Can not be merged, start a new event.
- var before = lst(hist.done)
- if (!before || !before.ranges)
- { pushSelectionToHistory(doc.sel, hist.done) }
- cur = {changes: [historyChangeFromChange(doc, change)],
- generation: hist.generation}
- hist.done.push(cur)
- while (hist.done.length > hist.undoDepth) {
- hist.done.shift()
- if (!hist.done[0].ranges) { hist.done.shift() }
- }
- }
- hist.done.push(selAfter)
- hist.generation = ++hist.maxGeneration
- hist.lastModTime = hist.lastSelTime = time
- hist.lastOp = hist.lastSelOp = opId
- hist.lastOrigin = hist.lastSelOrigin = change.origin
-
- if (!last) { signal(doc, "historyAdded") }
-}
-
-function selectionEventCanBeMerged(doc, origin, prev, sel) {
- var ch = origin.charAt(0)
- return ch == "*" ||
- ch == "+" &&
- prev.ranges.length == sel.ranges.length &&
- prev.somethingSelected() == sel.somethingSelected() &&
- new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)
-}
-
-// Called whenever the selection changes, sets the new selection as
-// the pending selection in the history, and pushes the old pending
-// selection into the 'done' array when it was significantly
-// different (in number of selected ranges, emptiness, or time).
-function addSelectionToHistory(doc, sel, opId, options) {
- var hist = doc.history, origin = options && options.origin
-
- // A new event is started when the previous origin does not match
- // the current, or the origins don't allow matching. Origins
- // starting with * are always merged, those starting with + are
- // merged when similar and close together in time.
- if (opId == hist.lastSelOp ||
- (origin && hist.lastSelOrigin == origin &&
- (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
- selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
- { hist.done[hist.done.length - 1] = sel }
- else
- { pushSelectionToHistory(sel, hist.done) }
-
- hist.lastSelTime = +new Date
- hist.lastSelOrigin = origin
- hist.lastSelOp = opId
- if (options && options.clearRedo !== false)
- { clearSelectionEvents(hist.undone) }
-}
-
-function pushSelectionToHistory(sel, dest) {
- var top = lst(dest)
- if (!(top && top.ranges && top.equals(sel)))
- { dest.push(sel) }
-}
-
-// Used to store marked span information in the history.
-function attachLocalSpans(doc, change, from, to) {
- var existing = change["spans_" + doc.id], n = 0
- doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {
- if (line.markedSpans)
- { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans }
- ++n
- })
-}
-
-// When un/re-doing restores text containing marked spans, those
-// that have been explicitly cleared should not be restored.
-function removeClearedSpans(spans) {
- if (!spans) { return null }
- var out
- for (var i = 0; i < spans.length; ++i) {
- if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } }
- else if (out) { out.push(spans[i]) }
- }
- return !out ? spans : out.length ? out : null
-}
-
-// Retrieve and filter the old marked spans stored in a change event.
-function getOldSpans(doc, change) {
- var found = change["spans_" + doc.id]
- if (!found) { return null }
- var nw = []
- for (var i = 0; i < change.text.length; ++i)
- { nw.push(removeClearedSpans(found[i])) }
- return nw
-}
-
-// Used for un/re-doing changes from the history. Combines the
-// result of computing the existing spans with the set of spans that
-// existed in the history (so that deleting around a span and then
-// undoing brings back the span).
-function mergeOldSpans(doc, change) {
- var old = getOldSpans(doc, change)
- var stretched = stretchSpansOverChange(doc, change)
- if (!old) { return stretched }
- if (!stretched) { return old }
-
- for (var i = 0; i < old.length; ++i) {
- var oldCur = old[i], stretchCur = stretched[i]
- if (oldCur && stretchCur) {
- spans: for (var j = 0; j < stretchCur.length; ++j) {
- var span = stretchCur[j]
- for (var k = 0; k < oldCur.length; ++k)
- { if (oldCur[k].marker == span.marker) { continue spans } }
- oldCur.push(span)
- }
- } else if (stretchCur) {
- old[i] = stretchCur
- }
- }
- return old
-}
-
-// Used both to provide a JSON-safe object in .getHistory, and, when
-// detaching a document, to split the history in two
-function copyHistoryArray(events, newGroup, instantiateSel) {
- var copy = []
- for (var i = 0; i < events.length; ++i) {
- var event = events[i]
- if (event.ranges) {
- copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event)
- continue
- }
- var changes = event.changes, newChanges = []
- copy.push({changes: newChanges})
- for (var j = 0; j < changes.length; ++j) {
- var change = changes[j], m = (void 0)
- newChanges.push({from: change.from, to: change.to, text: change.text})
- if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) {
- if (indexOf(newGroup, Number(m[1])) > -1) {
- lst(newChanges)[prop] = change[prop]
- delete change[prop]
- }
- } } }
- }
- }
- return copy
-}
-
-// The 'scroll' parameter given to many of these indicated whether
-// the new cursor position should be scrolled into view after
-// modifying the selection.
-
-// If shift is held or the extend flag is set, extends a range to
-// include a given position (and optionally a second position).
-// Otherwise, simply returns the range between the given positions.
-// Used for cursor motion and such.
-function extendRange(range, head, other, extend) {
- if (extend) {
- var anchor = range.anchor
- if (other) {
- var posBefore = cmp(head, anchor) < 0
- if (posBefore != (cmp(other, anchor) < 0)) {
- anchor = head
- head = other
- } else if (posBefore != (cmp(head, other) < 0)) {
- head = other
- }
- }
- return new Range(anchor, head)
- } else {
- return new Range(other || head, head)
- }
-}
-
-// Extend the primary selection range, discard the rest.
-function extendSelection(doc, head, other, options, extend) {
- if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend) }
- setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options)
-}
-
-// Extend all selections (pos is an array of selections with length
-// equal the number of selections)
-function extendSelections(doc, heads, options) {
- var out = []
- var extend = doc.cm && (doc.cm.display.shift || doc.extend)
- for (var i = 0; i < doc.sel.ranges.length; i++)
- { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) }
- var newSel = normalizeSelection(out, doc.sel.primIndex)
- setSelection(doc, newSel, options)
-}
-
-// Updates a single range in the selection.
-function replaceOneSelection(doc, i, range, options) {
- var ranges = doc.sel.ranges.slice(0)
- ranges[i] = range
- setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options)
-}
-
-// Reset the selection to a single range.
-function setSimpleSelection(doc, anchor, head, options) {
- setSelection(doc, simpleSelection(anchor, head), options)
-}
-
-// Give beforeSelectionChange handlers a change to influence a
-// selection update.
-function filterSelectionChange(doc, sel, options) {
- var obj = {
- ranges: sel.ranges,
- update: function(ranges) {
- var this$1 = this;
-
- this.ranges = []
- for (var i = 0; i < ranges.length; i++)
- { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
- clipPos(doc, ranges[i].head)) }
- },
- origin: options && options.origin
- }
- signal(doc, "beforeSelectionChange", doc, obj)
- if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) }
- if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) }
- else { return sel }
-}
-
-function setSelectionReplaceHistory(doc, sel, options) {
- var done = doc.history.done, last = lst(done)
- if (last && last.ranges) {
- done[done.length - 1] = sel
- setSelectionNoUndo(doc, sel, options)
- } else {
- setSelection(doc, sel, options)
- }
-}
-
-// Set a new selection.
-function setSelection(doc, sel, options) {
- setSelectionNoUndo(doc, sel, options)
- addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
-}
-
-function setSelectionNoUndo(doc, sel, options) {
- if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
- { sel = filterSelectionChange(doc, sel, options) }
-
- var bias = options && options.bias ||
- (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1)
- setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true))
-
- if (!(options && options.scroll === false) && doc.cm)
- { ensureCursorVisible(doc.cm) }
-}
-
-function setSelectionInner(doc, sel) {
- if (sel.equals(doc.sel)) { return }
-
- doc.sel = sel
-
- if (doc.cm) {
- doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true
- signalCursorActivity(doc.cm)
- }
- signalLater(doc, "cursorActivity", doc)
-}
-
-// Verify that the selection does not partially select any atomic
-// marked ranges.
-function reCheckSelection(doc) {
- setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false))
-}
-
-// Return a selection that does not partially select any atomic
-// ranges.
-function skipAtomicInSelection(doc, sel, bias, mayClear) {
- var out
- for (var i = 0; i < sel.ranges.length; i++) {
- var range = sel.ranges[i]
- var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]
- var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear)
- var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear)
- if (out || newAnchor != range.anchor || newHead != range.head) {
- if (!out) { out = sel.ranges.slice(0, i) }
- out[i] = new Range(newAnchor, newHead)
- }
- }
- return out ? normalizeSelection(out, sel.primIndex) : sel
-}
-
-function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
- var line = getLine(doc, pos.line)
- if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
- var sp = line.markedSpans[i], m = sp.marker
- if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
- (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
- if (mayClear) {
- signal(m, "beforeCursorEnter")
- if (m.explicitlyCleared) {
- if (!line.markedSpans) { break }
- else {--i; continue}
- }
- }
- if (!m.atomic) { continue }
-
- if (oldPos) {
- var near = m.find(dir < 0 ? 1 : -1), diff = (void 0)
- if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
- { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) }
- if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
- { return skipAtomicInner(doc, near, pos, dir, mayClear) }
- }
-
- var far = m.find(dir < 0 ? -1 : 1)
- if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
- { far = movePos(doc, far, dir, far.line == pos.line ? line : null) }
- return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
- }
- } }
- return pos
-}
-
-// Ensure a given position is not inside an atomic range.
-function skipAtomic(doc, pos, oldPos, bias, mayClear) {
- var dir = bias || 1
- var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
- (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
- skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
- (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true))
- if (!found) {
- doc.cantEdit = true
- return Pos(doc.first, 0)
- }
- return found
-}
-
-function movePos(doc, pos, dir, line) {
- if (dir < 0 && pos.ch == 0) {
- if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }
- else { return null }
- } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
- if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }
- else { return null }
- } else {
- return new Pos(pos.line, pos.ch + dir)
- }
-}
-
-function selectAll(cm) {
- cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll)
-}
-
-// UPDATING
-
-// Allow "beforeChange" event handlers to influence a change
-function filterChange(doc, change, update) {
- var obj = {
- canceled: false,
- from: change.from,
- to: change.to,
- text: change.text,
- origin: change.origin,
- cancel: function () { return obj.canceled = true; }
- }
- if (update) { obj.update = function (from, to, text, origin) {
- if (from) { obj.from = clipPos(doc, from) }
- if (to) { obj.to = clipPos(doc, to) }
- if (text) { obj.text = text }
- if (origin !== undefined) { obj.origin = origin }
- } }
- signal(doc, "beforeChange", doc, obj)
- if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) }
-
- if (obj.canceled) { return null }
- return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
-}
-
-// Apply a change to a document, and add it to the document's
-// history, and propagating it to all linked documents.
-function makeChange(doc, change, ignoreReadOnly) {
- if (doc.cm) {
- if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) }
- if (doc.cm.state.suppressEdits) { return }
- }
-
- if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
- change = filterChange(doc, change, true)
- if (!change) { return }
- }
-
- // Possibly split or suppress the update based on the presence
- // of read-only spans in its range.
- var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to)
- if (split) {
- for (var i = split.length - 1; i >= 0; --i)
- { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}) }
- } else {
- makeChangeInner(doc, change)
- }
-}
-
-function makeChangeInner(doc, change) {
- if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return }
- var selAfter = computeSelAfterChange(doc, change)
- addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN)
-
- makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change))
- var rebased = []
-
- linkedDocs(doc, function (doc, sharedHist) {
- if (!sharedHist && indexOf(rebased, doc.history) == -1) {
- rebaseHist(doc.history, change)
- rebased.push(doc.history)
- }
- makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change))
- })
-}
-
-// Revert a change stored in a document's history.
-function makeChangeFromHistory(doc, type, allowSelectionOnly) {
- if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return }
-
- var hist = doc.history, event, selAfter = doc.sel
- var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done
-
- // Verify that there is a useable event (so that ctrl-z won't
- // needlessly clear selection events)
- var i = 0
- for (; i < source.length; i++) {
- event = source[i]
- if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
- { break }
- }
- if (i == source.length) { return }
- hist.lastOrigin = hist.lastSelOrigin = null
-
- for (;;) {
- event = source.pop()
- if (event.ranges) {
- pushSelectionToHistory(event, dest)
- if (allowSelectionOnly && !event.equals(doc.sel)) {
- setSelection(doc, event, {clearRedo: false})
- return
- }
- selAfter = event
- }
- else { break }
- }
-
- // Build up a reverse change object to add to the opposite history
- // stack (redo when undoing, and vice versa).
- var antiChanges = []
- pushSelectionToHistory(selAfter, dest)
- dest.push({changes: antiChanges, generation: hist.generation})
- hist.generation = event.generation || ++hist.maxGeneration
-
- var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")
-
- var loop = function ( i ) {
- var change = event.changes[i]
- change.origin = type
- if (filter && !filterChange(doc, change, false)) {
- source.length = 0
- return {}
- }
-
- antiChanges.push(historyChangeFromChange(doc, change))
-
- var after = i ? computeSelAfterChange(doc, change) : lst(source)
- makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change))
- if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) }
- var rebased = []
-
- // Propagate to the linked documents
- linkedDocs(doc, function (doc, sharedHist) {
- if (!sharedHist && indexOf(rebased, doc.history) == -1) {
- rebaseHist(doc.history, change)
- rebased.push(doc.history)
- }
- makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change))
- })
- };
-
- for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {
- var returned = loop( i$1 );
-
- if ( returned ) return returned.v;
- }
-}
-
-// Sub-views need their line numbers shifted when text is added
-// above or below them in the parent document.
-function shiftDoc(doc, distance) {
- if (distance == 0) { return }
- doc.first += distance
- doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(
- Pos(range.anchor.line + distance, range.anchor.ch),
- Pos(range.head.line + distance, range.head.ch)
- ); }), doc.sel.primIndex)
- if (doc.cm) {
- regChange(doc.cm, doc.first, doc.first - distance, distance)
- for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
- { regLineChange(doc.cm, l, "gutter") }
- }
-}
-
-// More lower-level change function, handling only a single document
-// (not linked ones).
-function makeChangeSingleDoc(doc, change, selAfter, spans) {
- if (doc.cm && !doc.cm.curOp)
- { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) }
-
- if (change.to.line < doc.first) {
- shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line))
- return
- }
- if (change.from.line > doc.lastLine()) { return }
-
- // Clip the change to the size of this doc
- if (change.from.line < doc.first) {
- var shift = change.text.length - 1 - (doc.first - change.from.line)
- shiftDoc(doc, shift)
- change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
- text: [lst(change.text)], origin: change.origin}
- }
- var last = doc.lastLine()
- if (change.to.line > last) {
- change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
- text: [change.text[0]], origin: change.origin}
- }
-
- change.removed = getBetween(doc, change.from, change.to)
-
- if (!selAfter) { selAfter = computeSelAfterChange(doc, change) }
- if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) }
- else { updateDoc(doc, change, spans) }
- setSelectionNoUndo(doc, selAfter, sel_dontScroll)
-}
-
-// Handle the interaction of a change to a document with the editor
-// that this document is part of.
-function makeChangeSingleDocInEditor(cm, change, spans) {
- var doc = cm.doc, display = cm.display, from = change.from, to = change.to
-
- var recomputeMaxLength = false, checkWidthStart = from.line
- if (!cm.options.lineWrapping) {
- checkWidthStart = lineNo(visualLine(getLine(doc, from.line)))
- doc.iter(checkWidthStart, to.line + 1, function (line) {
- if (line == display.maxLine) {
- recomputeMaxLength = true
- return true
- }
- })
- }
-
- if (doc.sel.contains(change.from, change.to) > -1)
- { signalCursorActivity(cm) }
-
- updateDoc(doc, change, spans, estimateHeight(cm))
-
- if (!cm.options.lineWrapping) {
- doc.iter(checkWidthStart, from.line + change.text.length, function (line) {
- var len = lineLength(line)
- if (len > display.maxLineLength) {
- display.maxLine = line
- display.maxLineLength = len
- display.maxLineChanged = true
- recomputeMaxLength = false
- }
- })
- if (recomputeMaxLength) { cm.curOp.updateMaxLine = true }
- }
-
- retreatFrontier(doc, from.line)
- startWorker(cm, 400)
-
- var lendiff = change.text.length - (to.line - from.line) - 1
- // Remember that these lines changed, for updating the display
- if (change.full)
- { regChange(cm) }
- else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
- { regLineChange(cm, from.line, "text") }
- else
- { regChange(cm, from.line, to.line + 1, lendiff) }
-
- var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change")
- if (changeHandler || changesHandler) {
- var obj = {
- from: from, to: to,
- text: change.text,
- removed: change.removed,
- origin: change.origin
- }
- if (changeHandler) { signalLater(cm, "change", cm, obj) }
- if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) }
- }
- cm.display.selForContextMenu = null
-}
-
-function replaceRange(doc, code, from, to, origin) {
- if (!to) { to = from }
- if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp }
- if (typeof code == "string") { code = doc.splitLines(code) }
- makeChange(doc, {from: from, to: to, text: code, origin: origin})
-}
-
-// Rebasing/resetting history to deal with externally-sourced changes
-
-function rebaseHistSelSingle(pos, from, to, diff) {
- if (to < pos.line) {
- pos.line += diff
- } else if (from < pos.line) {
- pos.line = from
- pos.ch = 0
- }
-}
-
-// Tries to rebase an array of history events given a change in the
-// document. If the change touches the same lines as the event, the
-// event, and everything 'behind' it, is discarded. If the change is
-// before the event, the event's positions are updated. Uses a
-// copy-on-write scheme for the positions, to avoid having to
-// reallocate them all on every rebase, but also avoid problems with
-// shared position objects being unsafely updated.
-function rebaseHistArray(array, from, to, diff) {
- for (var i = 0; i < array.length; ++i) {
- var sub = array[i], ok = true
- if (sub.ranges) {
- if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true }
- for (var j = 0; j < sub.ranges.length; j++) {
- rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff)
- rebaseHistSelSingle(sub.ranges[j].head, from, to, diff)
- }
- continue
- }
- for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {
- var cur = sub.changes[j$1]
- if (to < cur.from.line) {
- cur.from = Pos(cur.from.line + diff, cur.from.ch)
- cur.to = Pos(cur.to.line + diff, cur.to.ch)
- } else if (from <= cur.to.line) {
- ok = false
- break
- }
- }
- if (!ok) {
- array.splice(0, i + 1)
- i = 0
- }
- }
-}
-
-function rebaseHist(hist, change) {
- var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1
- rebaseHistArray(hist.done, from, to, diff)
- rebaseHistArray(hist.undone, from, to, diff)
-}
-
-// Utility for applying a change to a line by handle or number,
-// returning the number and optionally registering the line as
-// changed.
-function changeLine(doc, handle, changeType, op) {
- var no = handle, line = handle
- if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) }
- else { no = lineNo(handle) }
- if (no == null) { return null }
- if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) }
- return line
-}
-
-// The document is represented as a BTree consisting of leaves, with
-// chunk of lines in them, and branches, with up to ten leaves or
-// other branch nodes below them. The top node is always a branch
-// node, and is the document object itself (meaning it has
-// additional methods and properties).
-//
-// All nodes have parent links. The tree is used both to go from
-// line numbers to line objects, and to go from objects to numbers.
-// It also indexes by height, and is used to convert between height
-// and line object, and to find the total height of the document.
-//
-// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
-
-function LeafChunk(lines) {
- var this$1 = this;
-
- this.lines = lines
- this.parent = null
- var height = 0
- for (var i = 0; i < lines.length; ++i) {
- lines[i].parent = this$1
- height += lines[i].height
- }
- this.height = height
-}
-
-LeafChunk.prototype = {
- chunkSize: function chunkSize() { return this.lines.length },
-
- // Remove the n lines at offset 'at'.
- removeInner: function removeInner(at, n) {
- var this$1 = this;
-
- for (var i = at, e = at + n; i < e; ++i) {
- var line = this$1.lines[i]
- this$1.height -= line.height
- cleanUpLine(line)
- signalLater(line, "delete")
- }
- this.lines.splice(at, n)
- },
-
- // Helper used to collapse a small branch into a single leaf.
- collapse: function collapse(lines) {
- lines.push.apply(lines, this.lines)
- },
-
- // Insert the given array of lines at offset 'at', count them as
- // having the given height.
- insertInner: function insertInner(at, lines, height) {
- var this$1 = this;
-
- this.height += height
- this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at))
- for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 }
- },
-
- // Used to iterate over a part of the tree.
- iterN: function iterN(at, n, op) {
- var this$1 = this;
-
- for (var e = at + n; at < e; ++at)
- { if (op(this$1.lines[at])) { return true } }
- }
-}
-
-function BranchChunk(children) {
- var this$1 = this;
-
- this.children = children
- var size = 0, height = 0
- for (var i = 0; i < children.length; ++i) {
- var ch = children[i]
- size += ch.chunkSize(); height += ch.height
- ch.parent = this$1
- }
- this.size = size
- this.height = height
- this.parent = null
-}
-
-BranchChunk.prototype = {
- chunkSize: function chunkSize() { return this.size },
-
- removeInner: function removeInner(at, n) {
- var this$1 = this;
-
- this.size -= n
- for (var i = 0; i < this.children.length; ++i) {
- var child = this$1.children[i], sz = child.chunkSize()
- if (at < sz) {
- var rm = Math.min(n, sz - at), oldHeight = child.height
- child.removeInner(at, rm)
- this$1.height -= oldHeight - child.height
- if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null }
- if ((n -= rm) == 0) { break }
- at = 0
- } else { at -= sz }
- }
- // If the result is smaller than 25 lines, ensure that it is a
- // single leaf node.
- if (this.size - n < 25 &&
- (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
- var lines = []
- this.collapse(lines)
- this.children = [new LeafChunk(lines)]
- this.children[0].parent = this
- }
- },
-
- collapse: function collapse(lines) {
- var this$1 = this;
-
- for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) }
- },
-
- insertInner: function insertInner(at, lines, height) {
- var this$1 = this;
-
- this.size += lines.length
- this.height += height
- for (var i = 0; i < this.children.length; ++i) {
- var child = this$1.children[i], sz = child.chunkSize()
- if (at <= sz) {
- child.insertInner(at, lines, height)
- if (child.lines && child.lines.length > 50) {
- // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
- // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
- var remaining = child.lines.length % 25 + 25
- for (var pos = remaining; pos < child.lines.length;) {
- var leaf = new LeafChunk(child.lines.slice(pos, pos += 25))
- child.height -= leaf.height
- this$1.children.splice(++i, 0, leaf)
- leaf.parent = this$1
- }
- child.lines = child.lines.slice(0, remaining)
- this$1.maybeSpill()
- }
- break
- }
- at -= sz
- }
- },
-
- // When a node has grown, check whether it should be split.
- maybeSpill: function maybeSpill() {
- if (this.children.length <= 10) { return }
- var me = this
- do {
- var spilled = me.children.splice(me.children.length - 5, 5)
- var sibling = new BranchChunk(spilled)
- if (!me.parent) { // Become the parent node
- var copy = new BranchChunk(me.children)
- copy.parent = me
- me.children = [copy, sibling]
- me = copy
- } else {
- me.size -= sibling.size
- me.height -= sibling.height
- var myIndex = indexOf(me.parent.children, me)
- me.parent.children.splice(myIndex + 1, 0, sibling)
- }
- sibling.parent = me.parent
- } while (me.children.length > 10)
- me.parent.maybeSpill()
- },
-
- iterN: function iterN(at, n, op) {
- var this$1 = this;
-
- for (var i = 0; i < this.children.length; ++i) {
- var child = this$1.children[i], sz = child.chunkSize()
- if (at < sz) {
- var used = Math.min(n, sz - at)
- if (child.iterN(at, used, op)) { return true }
- if ((n -= used) == 0) { break }
- at = 0
- } else { at -= sz }
- }
- }
-}
-
-// Line widgets are block elements displayed above or below a line.
-
-var LineWidget = function(doc, node, options) {
- var this$1 = this;
-
- if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))
- { this$1[opt] = options[opt] } } }
- this.doc = doc
- this.node = node
-};
-
-LineWidget.prototype.clear = function () {
- var this$1 = this;
-
- var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line)
- if (no == null || !ws) { return }
- for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } }
- if (!ws.length) { line.widgets = null }
- var height = widgetHeight(this)
- updateLineHeight(line, Math.max(0, line.height - height))
- if (cm) {
- runInOp(cm, function () {
- adjustScrollWhenAboveVisible(cm, line, -height)
- regLineChange(cm, no, "widget")
- })
- signalLater(cm, "lineWidgetCleared", cm, this, no)
- }
-};
-
-LineWidget.prototype.changed = function () {
- var this$1 = this;
-
- var oldH = this.height, cm = this.doc.cm, line = this.line
- this.height = null
- var diff = widgetHeight(this) - oldH
- if (!diff) { return }
- updateLineHeight(line, line.height + diff)
- if (cm) {
- runInOp(cm, function () {
- cm.curOp.forceUpdate = true
- adjustScrollWhenAboveVisible(cm, line, diff)
- signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line))
- })
- }
-};
-eventMixin(LineWidget)
-
-function adjustScrollWhenAboveVisible(cm, line, diff) {
- if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
- { addToScrollTop(cm, diff) }
-}
-
-function addLineWidget(doc, handle, node, options) {
- var widget = new LineWidget(doc, node, options)
- var cm = doc.cm
- if (cm && widget.noHScroll) { cm.display.alignWidgets = true }
- changeLine(doc, handle, "widget", function (line) {
- var widgets = line.widgets || (line.widgets = [])
- if (widget.insertAt == null) { widgets.push(widget) }
- else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) }
- widget.line = line
- if (cm && !lineIsHidden(doc, line)) {
- var aboveVisible = heightAtLine(line) < doc.scrollTop
- updateLineHeight(line, line.height + widgetHeight(widget))
- if (aboveVisible) { addToScrollTop(cm, widget.height) }
- cm.curOp.forceUpdate = true
- }
- return true
- })
- signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
- return widget
-}
-
-// TEXTMARKERS
-
-// Created with markText and setBookmark methods. A TextMarker is a
-// handle that can be used to clear or find a marked position in the
-// document. Line objects hold arrays (markedSpans) containing
-// {from, to, marker} object pointing to such marker objects, and
-// indicating that such a marker is present on that line. Multiple
-// lines may point to the same marker when it spans across lines.
-// The spans will have null for their from/to properties when the
-// marker continues beyond the start/end of the line. Markers have
-// links back to the lines they currently touch.
-
-// Collapsed markers have unique ids, in order to be able to order
-// them, which is needed for uniquely determining an outer marker
-// when they overlap (they may nest, but not partially overlap).
-var nextMarkerId = 0
-
-var TextMarker = function(doc, type) {
- this.lines = []
- this.type = type
- this.doc = doc
- this.id = ++nextMarkerId
-};
-
-// Clear the marker.
-TextMarker.prototype.clear = function () {
- var this$1 = this;
-
- if (this.explicitlyCleared) { return }
- var cm = this.doc.cm, withOp = cm && !cm.curOp
- if (withOp) { startOperation(cm) }
- if (hasHandler(this, "clear")) {
- var found = this.find()
- if (found) { signalLater(this, "clear", found.from, found.to) }
- }
- var min = null, max = null
- for (var i = 0; i < this.lines.length; ++i) {
- var line = this$1.lines[i]
- var span = getMarkedSpanFor(line.markedSpans, this$1)
- if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") }
- else if (cm) {
- if (span.to != null) { max = lineNo(line) }
- if (span.from != null) { min = lineNo(line) }
- }
- line.markedSpans = removeMarkedSpan(line.markedSpans, span)
- if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm)
- { updateLineHeight(line, textHeight(cm.display)) }
- }
- if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {
- var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual)
- if (len > cm.display.maxLineLength) {
- cm.display.maxLine = visual
- cm.display.maxLineLength = len
- cm.display.maxLineChanged = true
- }
- } }
-
- if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) }
- this.lines.length = 0
- this.explicitlyCleared = true
- if (this.atomic && this.doc.cantEdit) {
- this.doc.cantEdit = false
- if (cm) { reCheckSelection(cm.doc) }
- }
- if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) }
- if (withOp) { endOperation(cm) }
- if (this.parent) { this.parent.clear() }
-};
-
-// Find the position of the marker in the document. Returns a {from,
-// to} object by default. Side can be passed to get a specific side
-// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
-// Pos objects returned contain a line object, rather than a line
-// number (used to prevent looking up the same line twice).
-TextMarker.prototype.find = function (side, lineObj) {
- var this$1 = this;
-
- if (side == null && this.type == "bookmark") { side = 1 }
- var from, to
- for (var i = 0; i < this.lines.length; ++i) {
- var line = this$1.lines[i]
- var span = getMarkedSpanFor(line.markedSpans, this$1)
- if (span.from != null) {
- from = Pos(lineObj ? line : lineNo(line), span.from)
- if (side == -1) { return from }
- }
- if (span.to != null) {
- to = Pos(lineObj ? line : lineNo(line), span.to)
- if (side == 1) { return to }
- }
- }
- return from && {from: from, to: to}
-};
-
-// Signals that the marker's widget changed, and surrounding layout
-// should be recomputed.
-TextMarker.prototype.changed = function () {
- var this$1 = this;
-
- var pos = this.find(-1, true), widget = this, cm = this.doc.cm
- if (!pos || !cm) { return }
- runInOp(cm, function () {
- var line = pos.line, lineN = lineNo(pos.line)
- var view = findViewForLine(cm, lineN)
- if (view) {
- clearLineMeasurementCacheFor(view)
- cm.curOp.selectionChanged = cm.curOp.forceUpdate = true
- }
- cm.curOp.updateMaxLine = true
- if (!lineIsHidden(widget.doc, line) && widget.height != null) {
- var oldHeight = widget.height
- widget.height = null
- var dHeight = widgetHeight(widget) - oldHeight
- if (dHeight)
- { updateLineHeight(line, line.height + dHeight) }
- }
- signalLater(cm, "markerChanged", cm, this$1)
- })
-};
-
-TextMarker.prototype.attachLine = function (line) {
- if (!this.lines.length && this.doc.cm) {
- var op = this.doc.cm.curOp
- if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
- { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) }
- }
- this.lines.push(line)
-};
-
-TextMarker.prototype.detachLine = function (line) {
- this.lines.splice(indexOf(this.lines, line), 1)
- if (!this.lines.length && this.doc.cm) {
- var op = this.doc.cm.curOp
- ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this)
- }
-};
-eventMixin(TextMarker)
-
-// Create a marker, wire it up to the right lines, and
-function markText(doc, from, to, options, type) {
- // Shared markers (across linked documents) are handled separately
- // (markTextShared will call out to this again, once per
- // document).
- if (options && options.shared) { return markTextShared(doc, from, to, options, type) }
- // Ensure we are in an operation.
- if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }
-
- var marker = new TextMarker(doc, type), diff = cmp(from, to)
- if (options) { copyObj(options, marker, false) }
- // Don't connect empty markers unless clearWhenEmpty is false
- if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
- { return marker }
- if (marker.replacedWith) {
- // Showing up as a widget implies collapsed (widget replaces text)
- marker.collapsed = true
- marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget")
- if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") }
- if (options.insertLeft) { marker.widgetNode.insertLeft = true }
- }
- if (marker.collapsed) {
- if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
- from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
- { throw new Error("Inserting collapsed marker partially overlapping an existing one") }
- seeCollapsedSpans()
- }
-
- if (marker.addToHistory)
- { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) }
-
- var curLine = from.line, cm = doc.cm, updateMaxLine
- doc.iter(curLine, to.line + 1, function (line) {
- if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
- { updateMaxLine = true }
- if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) }
- addMarkedSpan(line, new MarkedSpan(marker,
- curLine == from.line ? from.ch : null,
- curLine == to.line ? to.ch : null))
- ++curLine
- })
- // lineIsHidden depends on the presence of the spans, so needs a second pass
- if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {
- if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) }
- }) }
-
- if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) }
-
- if (marker.readOnly) {
- seeReadOnlySpans()
- if (doc.history.done.length || doc.history.undone.length)
- { doc.clearHistory() }
- }
- if (marker.collapsed) {
- marker.id = ++nextMarkerId
- marker.atomic = true
- }
- if (cm) {
- // Sync editor state
- if (updateMaxLine) { cm.curOp.updateMaxLine = true }
- if (marker.collapsed)
- { regChange(cm, from.line, to.line + 1) }
- else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css)
- { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } }
- if (marker.atomic) { reCheckSelection(cm.doc) }
- signalLater(cm, "markerAdded", cm, marker)
- }
- return marker
-}
-
-// SHARED TEXTMARKERS
-
-// A shared marker spans multiple linked documents. It is
-// implemented as a meta-marker-object controlling multiple normal
-// markers.
-var SharedTextMarker = function(markers, primary) {
- var this$1 = this;
-
- this.markers = markers
- this.primary = primary
- for (var i = 0; i < markers.length; ++i)
- { markers[i].parent = this$1 }
-};
-
-SharedTextMarker.prototype.clear = function () {
- var this$1 = this;
-
- if (this.explicitlyCleared) { return }
- this.explicitlyCleared = true
- for (var i = 0; i < this.markers.length; ++i)
- { this$1.markers[i].clear() }
- signalLater(this, "clear")
-};
-
-SharedTextMarker.prototype.find = function (side, lineObj) {
- return this.primary.find(side, lineObj)
-};
-eventMixin(SharedTextMarker)
-
-function markTextShared(doc, from, to, options, type) {
- options = copyObj(options)
- options.shared = false
- var markers = [markText(doc, from, to, options, type)], primary = markers[0]
- var widget = options.widgetNode
- linkedDocs(doc, function (doc) {
- if (widget) { options.widgetNode = widget.cloneNode(true) }
- markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type))
- for (var i = 0; i < doc.linked.length; ++i)
- { if (doc.linked[i].isParent) { return } }
- primary = lst(markers)
- })
- return new SharedTextMarker(markers, primary)
-}
-
-function findSharedMarkers(doc) {
- return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })
-}
-
-function copySharedMarkers(doc, markers) {
- for (var i = 0; i < markers.length; i++) {
- var marker = markers[i], pos = marker.find()
- var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to)
- if (cmp(mFrom, mTo)) {
- var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type)
- marker.markers.push(subMark)
- subMark.parent = marker
- }
- }
-}
-
-function detachSharedMarkers(markers) {
- var loop = function ( i ) {
- var marker = markers[i], linked = [marker.primary.doc]
- linkedDocs(marker.primary.doc, function (d) { return linked.push(d); })
- for (var j = 0; j < marker.markers.length; j++) {
- var subMarker = marker.markers[j]
- if (indexOf(linked, subMarker.doc) == -1) {
- subMarker.parent = null
- marker.markers.splice(j--, 1)
- }
- }
- };
-
- for (var i = 0; i < markers.length; i++) loop( i );
-}
-
-var nextDocId = 0
-var Doc = function(text, mode, firstLine, lineSep, direction) {
- if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }
- if (firstLine == null) { firstLine = 0 }
-
- BranchChunk.call(this, [new LeafChunk([new Line("", null)])])
- this.first = firstLine
- this.scrollTop = this.scrollLeft = 0
- this.cantEdit = false
- this.cleanGeneration = 1
- this.modeFrontier = this.highlightFrontier = firstLine
- var start = Pos(firstLine, 0)
- this.sel = simpleSelection(start)
- this.history = new History(null)
- this.id = ++nextDocId
- this.modeOption = mode
- this.lineSep = lineSep
- this.direction = (direction == "rtl") ? "rtl" : "ltr"
- this.extend = false
-
- if (typeof text == "string") { text = this.splitLines(text) }
- updateDoc(this, {from: start, to: start, text: text})
- setSelection(this, simpleSelection(start), sel_dontScroll)
-}
-
-Doc.prototype = createObj(BranchChunk.prototype, {
- constructor: Doc,
- // Iterate over the document. Supports two forms -- with only one
- // argument, it calls that for each line in the document. With
- // three, it iterates over the range given by the first two (with
- // the second being non-inclusive).
- iter: function(from, to, op) {
- if (op) { this.iterN(from - this.first, to - from, op) }
- else { this.iterN(this.first, this.first + this.size, from) }
- },
-
- // Non-public interface for adding and removing lines.
- insert: function(at, lines) {
- var height = 0
- for (var i = 0; i < lines.length; ++i) { height += lines[i].height }
- this.insertInner(at - this.first, lines, height)
- },
- remove: function(at, n) { this.removeInner(at - this.first, n) },
-
- // From here, the methods are part of the public interface. Most
- // are also available from CodeMirror (editor) instances.
-
- getValue: function(lineSep) {
- var lines = getLines(this, this.first, this.first + this.size)
- if (lineSep === false) { return lines }
- return lines.join(lineSep || this.lineSeparator())
- },
- setValue: docMethodOp(function(code) {
- var top = Pos(this.first, 0), last = this.first + this.size - 1
- makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
- text: this.splitLines(code), origin: "setValue", full: true}, true)
- if (this.cm) { scrollToCoords(this.cm, 0, 0) }
- setSelection(this, simpleSelection(top), sel_dontScroll)
- }),
- replaceRange: function(code, from, to, origin) {
- from = clipPos(this, from)
- to = to ? clipPos(this, to) : from
- replaceRange(this, code, from, to, origin)
- },
- getRange: function(from, to, lineSep) {
- var lines = getBetween(this, clipPos(this, from), clipPos(this, to))
- if (lineSep === false) { return lines }
- return lines.join(lineSep || this.lineSeparator())
- },
-
- getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},
-
- getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},
- getLineNumber: function(line) {return lineNo(line)},
-
- getLineHandleVisualStart: function(line) {
- if (typeof line == "number") { line = getLine(this, line) }
- return visualLine(line)
- },
-
- lineCount: function() {return this.size},
- firstLine: function() {return this.first},
- lastLine: function() {return this.first + this.size - 1},
-
- clipPos: function(pos) {return clipPos(this, pos)},
-
- getCursor: function(start) {
- var range = this.sel.primary(), pos
- if (start == null || start == "head") { pos = range.head }
- else if (start == "anchor") { pos = range.anchor }
- else if (start == "end" || start == "to" || start === false) { pos = range.to() }
- else { pos = range.from() }
- return pos
- },
- listSelections: function() { return this.sel.ranges },
- somethingSelected: function() {return this.sel.somethingSelected()},
-
- setCursor: docMethodOp(function(line, ch, options) {
- setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options)
- }),
- setSelection: docMethodOp(function(anchor, head, options) {
- setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options)
- }),
- extendSelection: docMethodOp(function(head, other, options) {
- extendSelection(this, clipPos(this, head), other && clipPos(this, other), options)
- }),
- extendSelections: docMethodOp(function(heads, options) {
- extendSelections(this, clipPosArray(this, heads), options)
- }),
- extendSelectionsBy: docMethodOp(function(f, options) {
- var heads = map(this.sel.ranges, f)
- extendSelections(this, clipPosArray(this, heads), options)
- }),
- setSelections: docMethodOp(function(ranges, primary, options) {
- var this$1 = this;
-
- if (!ranges.length) { return }
- var out = []
- for (var i = 0; i < ranges.length; i++)
- { out[i] = new Range(clipPos(this$1, ranges[i].anchor),
- clipPos(this$1, ranges[i].head)) }
- if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) }
- setSelection(this, normalizeSelection(out, primary), options)
- }),
- addSelection: docMethodOp(function(anchor, head, options) {
- var ranges = this.sel.ranges.slice(0)
- ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)))
- setSelection(this, normalizeSelection(ranges, ranges.length - 1), options)
- }),
-
- getSelection: function(lineSep) {
- var this$1 = this;
-
- var ranges = this.sel.ranges, lines
- for (var i = 0; i < ranges.length; i++) {
- var sel = getBetween(this$1, ranges[i].from(), ranges[i].to())
- lines = lines ? lines.concat(sel) : sel
- }
- if (lineSep === false) { return lines }
- else { return lines.join(lineSep || this.lineSeparator()) }
- },
- getSelections: function(lineSep) {
- var this$1 = this;
-
- var parts = [], ranges = this.sel.ranges
- for (var i = 0; i < ranges.length; i++) {
- var sel = getBetween(this$1, ranges[i].from(), ranges[i].to())
- if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) }
- parts[i] = sel
- }
- return parts
- },
- replaceSelection: function(code, collapse, origin) {
- var dup = []
- for (var i = 0; i < this.sel.ranges.length; i++)
- { dup[i] = code }
- this.replaceSelections(dup, collapse, origin || "+input")
- },
- replaceSelections: docMethodOp(function(code, collapse, origin) {
- var this$1 = this;
-
- var changes = [], sel = this.sel
- for (var i = 0; i < sel.ranges.length; i++) {
- var range = sel.ranges[i]
- changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin}
- }
- var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse)
- for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)
- { makeChange(this$1, changes[i$1]) }
- if (newSel) { setSelectionReplaceHistory(this, newSel) }
- else if (this.cm) { ensureCursorVisible(this.cm) }
- }),
- undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}),
- redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}),
- undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}),
- redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}),
-
- setExtending: function(val) {this.extend = val},
- getExtending: function() {return this.extend},
-
- historySize: function() {
- var hist = this.history, done = 0, undone = 0
- for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } }
- for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } }
- return {undo: done, redo: undone}
- },
- clearHistory: function() {this.history = new History(this.history.maxGeneration)},
-
- markClean: function() {
- this.cleanGeneration = this.changeGeneration(true)
- },
- changeGeneration: function(forceSplit) {
- if (forceSplit)
- { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null }
- return this.history.generation
- },
- isClean: function (gen) {
- return this.history.generation == (gen || this.cleanGeneration)
- },
-
- getHistory: function() {
- return {done: copyHistoryArray(this.history.done),
- undone: copyHistoryArray(this.history.undone)}
- },
- setHistory: function(histData) {
- var hist = this.history = new History(this.history.maxGeneration)
- hist.done = copyHistoryArray(histData.done.slice(0), null, true)
- hist.undone = copyHistoryArray(histData.undone.slice(0), null, true)
- },
-
- setGutterMarker: docMethodOp(function(line, gutterID, value) {
- return changeLine(this, line, "gutter", function (line) {
- var markers = line.gutterMarkers || (line.gutterMarkers = {})
- markers[gutterID] = value
- if (!value && isEmpty(markers)) { line.gutterMarkers = null }
- return true
- })
- }),
-
- clearGutter: docMethodOp(function(gutterID) {
- var this$1 = this;
-
- this.iter(function (line) {
- if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
- changeLine(this$1, line, "gutter", function () {
- line.gutterMarkers[gutterID] = null
- if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null }
- return true
- })
- }
- })
- }),
-
- lineInfo: function(line) {
- var n
- if (typeof line == "number") {
- if (!isLine(this, line)) { return null }
- n = line
- line = getLine(this, line)
- if (!line) { return null }
- } else {
- n = lineNo(line)
- if (n == null) { return null }
- }
- return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
- textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
- widgets: line.widgets}
- },
-
- addLineClass: docMethodOp(function(handle, where, cls) {
- return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
- var prop = where == "text" ? "textClass"
- : where == "background" ? "bgClass"
- : where == "gutter" ? "gutterClass" : "wrapClass"
- if (!line[prop]) { line[prop] = cls }
- else if (classTest(cls).test(line[prop])) { return false }
- else { line[prop] += " " + cls }
- return true
- })
- }),
- removeLineClass: docMethodOp(function(handle, where, cls) {
- return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) {
- var prop = where == "text" ? "textClass"
- : where == "background" ? "bgClass"
- : where == "gutter" ? "gutterClass" : "wrapClass"
- var cur = line[prop]
- if (!cur) { return false }
- else if (cls == null) { line[prop] = null }
- else {
- var found = cur.match(classTest(cls))
- if (!found) { return false }
- var end = found.index + found[0].length
- line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null
- }
- return true
- })
- }),
-
- addLineWidget: docMethodOp(function(handle, node, options) {
- return addLineWidget(this, handle, node, options)
- }),
- removeLineWidget: function(widget) { widget.clear() },
-
- markText: function(from, to, options) {
- return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range")
- },
- setBookmark: function(pos, options) {
- var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
- insertLeft: options && options.insertLeft,
- clearWhenEmpty: false, shared: options && options.shared,
- handleMouseEvents: options && options.handleMouseEvents}
- pos = clipPos(this, pos)
- return markText(this, pos, pos, realOpts, "bookmark")
- },
- findMarksAt: function(pos) {
- pos = clipPos(this, pos)
- var markers = [], spans = getLine(this, pos.line).markedSpans
- if (spans) { for (var i = 0; i < spans.length; ++i) {
- var span = spans[i]
- if ((span.from == null || span.from <= pos.ch) &&
- (span.to == null || span.to >= pos.ch))
- { markers.push(span.marker.parent || span.marker) }
- } }
- return markers
- },
- findMarks: function(from, to, filter) {
- from = clipPos(this, from); to = clipPos(this, to)
- var found = [], lineNo = from.line
- this.iter(from.line, to.line + 1, function (line) {
- var spans = line.markedSpans
- if (spans) { for (var i = 0; i < spans.length; i++) {
- var span = spans[i]
- if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||
- span.from == null && lineNo != from.line ||
- span.from != null && lineNo == to.line && span.from >= to.ch) &&
- (!filter || filter(span.marker)))
- { found.push(span.marker.parent || span.marker) }
- } }
- ++lineNo
- })
- return found
- },
- getAllMarks: function() {
- var markers = []
- this.iter(function (line) {
- var sps = line.markedSpans
- if (sps) { for (var i = 0; i < sps.length; ++i)
- { if (sps[i].from != null) { markers.push(sps[i].marker) } } }
- })
- return markers
- },
-
- posFromIndex: function(off) {
- var ch, lineNo = this.first, sepSize = this.lineSeparator().length
- this.iter(function (line) {
- var sz = line.text.length + sepSize
- if (sz > off) { ch = off; return true }
- off -= sz
- ++lineNo
- })
- return clipPos(this, Pos(lineNo, ch))
- },
- indexFromPos: function (coords) {
- coords = clipPos(this, coords)
- var index = coords.ch
- if (coords.line < this.first || coords.ch < 0) { return 0 }
- var sepSize = this.lineSeparator().length
- this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value
- index += line.text.length + sepSize
- })
- return index
- },
-
- copy: function(copyHistory) {
- var doc = new Doc(getLines(this, this.first, this.first + this.size),
- this.modeOption, this.first, this.lineSep, this.direction)
- doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft
- doc.sel = this.sel
- doc.extend = false
- if (copyHistory) {
- doc.history.undoDepth = this.history.undoDepth
- doc.setHistory(this.getHistory())
- }
- return doc
- },
-
- linkedDoc: function(options) {
- if (!options) { options = {} }
- var from = this.first, to = this.first + this.size
- if (options.from != null && options.from > from) { from = options.from }
- if (options.to != null && options.to < to) { to = options.to }
- var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction)
- if (options.sharedHist) { copy.history = this.history
- ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist})
- copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]
- copySharedMarkers(copy, findSharedMarkers(this))
- return copy
- },
- unlinkDoc: function(other) {
- var this$1 = this;
-
- if (other instanceof CodeMirror) { other = other.doc }
- if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {
- var link = this$1.linked[i]
- if (link.doc != other) { continue }
- this$1.linked.splice(i, 1)
- other.unlinkDoc(this$1)
- detachSharedMarkers(findSharedMarkers(this$1))
- break
- } }
- // If the histories were shared, split them again
- if (other.history == this.history) {
- var splitIds = [other.id]
- linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true)
- other.history = new History(null)
- other.history.done = copyHistoryArray(this.history.done, splitIds)
- other.history.undone = copyHistoryArray(this.history.undone, splitIds)
- }
- },
- iterLinkedDocs: function(f) {linkedDocs(this, f)},
-
- getMode: function() {return this.mode},
- getEditor: function() {return this.cm},
-
- splitLines: function(str) {
- if (this.lineSep) { return str.split(this.lineSep) }
- return splitLinesAuto(str)
- },
- lineSeparator: function() { return this.lineSep || "\n" },
-
- setDirection: docMethodOp(function (dir) {
- if (dir != "rtl") { dir = "ltr" }
- if (dir == this.direction) { return }
- this.direction = dir
- this.iter(function (line) { return line.order = null; })
- if (this.cm) { directionChanged(this.cm) }
- })
-})
-
-// Public alias.
-Doc.prototype.eachLine = Doc.prototype.iter
-
-// Kludge to work around strange IE behavior where it'll sometimes
-// re-fire a series of drag-related events right after the drop (#1551)
-var lastDrop = 0
-
-function onDrop(e) {
- var cm = this
- clearDragCursor(cm)
- if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
- { return }
- e_preventDefault(e)
- if (ie) { lastDrop = +new Date }
- var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files
- if (!pos || cm.isReadOnly()) { return }
- // Might be a file drop, in which case we simply extract the text
- // and insert it.
- if (files && files.length && window.FileReader && window.File) {
- var n = files.length, text = Array(n), read = 0
- var loadFile = function (file, i) {
- if (cm.options.allowDropFileTypes &&
- indexOf(cm.options.allowDropFileTypes, file.type) == -1)
- { return }
-
- var reader = new FileReader
- reader.onload = operation(cm, function () {
- var content = reader.result
- if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" }
- text[i] = content
- if (++read == n) {
- pos = clipPos(cm.doc, pos)
- var change = {from: pos, to: pos,
- text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
- origin: "paste"}
- makeChange(cm.doc, change)
- setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)))
- }
- })
- reader.readAsText(file)
- }
- for (var i = 0; i < n; ++i) { loadFile(files[i], i) }
- } else { // Normal drop
- // Don't do a replace if the drop happened inside of the selected text.
- if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
- cm.state.draggingText(e)
- // Ensure the editor is re-focused
- setTimeout(function () { return cm.display.input.focus(); }, 20)
- return
- }
- try {
- var text$1 = e.dataTransfer.getData("Text")
- if (text$1) {
- var selected
- if (cm.state.draggingText && !cm.state.draggingText.copy)
- { selected = cm.listSelections() }
- setSelectionNoUndo(cm.doc, simpleSelection(pos, pos))
- if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)
- { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } }
- cm.replaceSelection(text$1, "around", "paste")
- cm.display.input.focus()
- }
- }
- catch(e){}
- }
-}
-
-function onDragStart(cm, e) {
- if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }
- if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }
-
- e.dataTransfer.setData("Text", cm.getSelection())
- e.dataTransfer.effectAllowed = "copyMove"
-
- // Use dummy image instead of default browsers image.
- // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
- if (e.dataTransfer.setDragImage && !safari) {
- var img = elt("img", null, null, "position: fixed; left: 0; top: 0;")
- img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
- if (presto) {
- img.width = img.height = 1
- cm.display.wrapper.appendChild(img)
- // Force a relayout, or Opera won't use our image for some obscure reason
- img._top = img.offsetTop
- }
- e.dataTransfer.setDragImage(img, 0, 0)
- if (presto) { img.parentNode.removeChild(img) }
- }
-}
-
-function onDragOver(cm, e) {
- var pos = posFromMouse(cm, e)
- if (!pos) { return }
- var frag = document.createDocumentFragment()
- drawSelectionCursor(cm, pos, frag)
- if (!cm.display.dragCursor) {
- cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors")
- cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv)
- }
- removeChildrenAndAdd(cm.display.dragCursor, frag)
-}
-
-function clearDragCursor(cm) {
- if (cm.display.dragCursor) {
- cm.display.lineSpace.removeChild(cm.display.dragCursor)
- cm.display.dragCursor = null
- }
-}
-
-// These must be handled carefully, because naively registering a
-// handler for each editor will cause the editors to never be
-// garbage collected.
-
-function forEachCodeMirror(f) {
- if (!document.getElementsByClassName) { return }
- var byClass = document.getElementsByClassName("CodeMirror")
- for (var i = 0; i < byClass.length; i++) {
- var cm = byClass[i].CodeMirror
- if (cm) { f(cm) }
- }
-}
-
-var globalsRegistered = false
-function ensureGlobalHandlers() {
- if (globalsRegistered) { return }
- registerGlobalHandlers()
- globalsRegistered = true
-}
-function registerGlobalHandlers() {
- // When the window resizes, we need to refresh active editors.
- var resizeTimer
- on(window, "resize", function () {
- if (resizeTimer == null) { resizeTimer = setTimeout(function () {
- resizeTimer = null
- forEachCodeMirror(onResize)
- }, 100) }
- })
- // When the window loses focus, we want to show the editor as blurred
- on(window, "blur", function () { return forEachCodeMirror(onBlur); })
-}
-// Called when the window resizes
-function onResize(cm) {
- var d = cm.display
- if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth)
- { return }
- // Might be a text scaling operation, clear size caches.
- d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null
- d.scrollbarsClipped = false
- cm.setSize()
-}
-
-var keyNames = {
- 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
- 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
- 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
- 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
- 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
- 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
- 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
- 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
-}
-
-// Number keys
-for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) }
-// Alphabetic keys
-for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) }
-// Function keys
-for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 }
-
-var keyMap = {}
-
-keyMap.basic = {
- "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
- "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
- "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
- "Tab": "defaultTab", "Shift-Tab": "indentAuto",
- "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
- "Esc": "singleSelection"
-}
-// Note that the save and find-related commands aren't defined by
-// default. User code or addons can define them. Unknown commands
-// are simply ignored.
-keyMap.pcDefault = {
- "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
- "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
- "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
- "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
- "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
- "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
- "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
- fallthrough: "basic"
-}
-// Very basic readline/emacs-style bindings, which are standard on Mac.
-keyMap.emacsy = {
- "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
- "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
- "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
- "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars",
- "Ctrl-O": "openLine"
-}
-keyMap.macDefault = {
- "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
- "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
- "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
- "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
- "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
- "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
- "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
- fallthrough: ["basic", "emacsy"]
-}
-keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault
-
-// KEYMAP DISPATCH
-
-function normalizeKeyName(name) {
- var parts = name.split(/-(?!$)/)
- name = parts[parts.length - 1]
- var alt, ctrl, shift, cmd
- for (var i = 0; i < parts.length - 1; i++) {
- var mod = parts[i]
- if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true }
- else if (/^a(lt)?$/i.test(mod)) { alt = true }
- else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true }
- else if (/^s(hift)?$/i.test(mod)) { shift = true }
- else { throw new Error("Unrecognized modifier name: " + mod) }
- }
- if (alt) { name = "Alt-" + name }
- if (ctrl) { name = "Ctrl-" + name }
- if (cmd) { name = "Cmd-" + name }
- if (shift) { name = "Shift-" + name }
- return name
-}
-
-// This is a kludge to keep keymaps mostly working as raw objects
-// (backwards compatibility) while at the same time support features
-// like normalization and multi-stroke key bindings. It compiles a
-// new normalized keymap, and then updates the old object to reflect
-// this.
-function normalizeKeyMap(keymap) {
- var copy = {}
- for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {
- var value = keymap[keyname]
- if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }
- if (value == "...") { delete keymap[keyname]; continue }
-
- var keys = map(keyname.split(" "), normalizeKeyName)
- for (var i = 0; i < keys.length; i++) {
- var val = (void 0), name = (void 0)
- if (i == keys.length - 1) {
- name = keys.join(" ")
- val = value
- } else {
- name = keys.slice(0, i + 1).join(" ")
- val = "..."
- }
- var prev = copy[name]
- if (!prev) { copy[name] = val }
- else if (prev != val) { throw new Error("Inconsistent bindings for " + name) }
- }
- delete keymap[keyname]
- } }
- for (var prop in copy) { keymap[prop] = copy[prop] }
- return keymap
-}
-
-function lookupKey(key, map, handle, context) {
- map = getKeyMap(map)
- var found = map.call ? map.call(key, context) : map[key]
- if (found === false) { return "nothing" }
- if (found === "...") { return "multi" }
- if (found != null && handle(found)) { return "handled" }
-
- if (map.fallthrough) {
- if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
- { return lookupKey(key, map.fallthrough, handle, context) }
- for (var i = 0; i < map.fallthrough.length; i++) {
- var result = lookupKey(key, map.fallthrough[i], handle, context)
- if (result) { return result }
- }
- }
-}
-
-// Modifier key presses don't count as 'real' key presses for the
-// purpose of keymap fallthrough.
-function isModifierKey(value) {
- var name = typeof value == "string" ? value : keyNames[value.keyCode]
- return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"
-}
-
-function addModifierNames(name, event, noShift) {
- var base = name
- if (event.altKey && base != "Alt") { name = "Alt-" + name }
- if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name }
- if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name }
- if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name }
- return name
-}
-
-// Look up the name of a key as indicated by an event object.
-function keyName(event, noShift) {
- if (presto && event.keyCode == 34 && event["char"]) { return false }
- var name = keyNames[event.keyCode]
- if (name == null || event.altGraphKey) { return false }
- return addModifierNames(name, event, noShift)
-}
-
-function getKeyMap(val) {
- return typeof val == "string" ? keyMap[val] : val
-}
-
-// Helper for deleting text near the selection(s), used to implement
-// backspace, delete, and similar functionality.
-function deleteNearSelection(cm, compute) {
- var ranges = cm.doc.sel.ranges, kill = []
- // Build up a set of ranges to kill first, merging overlapping
- // ranges.
- for (var i = 0; i < ranges.length; i++) {
- var toKill = compute(ranges[i])
- while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
- var replaced = kill.pop()
- if (cmp(replaced.from, toKill.from) < 0) {
- toKill.from = replaced.from
- break
- }
- }
- kill.push(toKill)
- }
- // Next, remove those actual ranges.
- runInOp(cm, function () {
- for (var i = kill.length - 1; i >= 0; i--)
- { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") }
- ensureCursorVisible(cm)
- })
-}
-
-// Commands are parameter-less actions that can be performed on an
-// editor, mostly used for keybindings.
-var commands = {
- selectAll: selectAll,
- singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); },
- killLine: function (cm) { return deleteNearSelection(cm, function (range) {
- if (range.empty()) {
- var len = getLine(cm.doc, range.head.line).text.length
- if (range.head.ch == len && range.head.line < cm.lastLine())
- { return {from: range.head, to: Pos(range.head.line + 1, 0)} }
- else
- { return {from: range.head, to: Pos(range.head.line, len)} }
- } else {
- return {from: range.from(), to: range.to()}
- }
- }); },
- deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({
- from: Pos(range.from().line, 0),
- to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
- }); }); },
- delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({
- from: Pos(range.from().line, 0), to: range.from()
- }); }); },
- delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {
- var top = cm.charCoords(range.head, "div").top + 5
- var leftPos = cm.coordsChar({left: 0, top: top}, "div")
- return {from: leftPos, to: range.from()}
- }); },
- delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {
- var top = cm.charCoords(range.head, "div").top + 5
- var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
- return {from: range.from(), to: rightPos }
- }); },
- undo: function (cm) { return cm.undo(); },
- redo: function (cm) { return cm.redo(); },
- undoSelection: function (cm) { return cm.undoSelection(); },
- redoSelection: function (cm) { return cm.redoSelection(); },
- goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },
- goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },
- goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },
- {origin: "+move", bias: 1}
- ); },
- goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },
- {origin: "+move", bias: 1}
- ); },
- goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },
- {origin: "+move", bias: -1}
- ); },
- goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
- var top = cm.cursorCoords(range.head, "div").top + 5
- return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
- }, sel_move); },
- goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
- var top = cm.cursorCoords(range.head, "div").top + 5
- return cm.coordsChar({left: 0, top: top}, "div")
- }, sel_move); },
- goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
- var top = cm.cursorCoords(range.head, "div").top + 5
- var pos = cm.coordsChar({left: 0, top: top}, "div")
- if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
- return pos
- }, sel_move); },
- goLineUp: function (cm) { return cm.moveV(-1, "line"); },
- goLineDown: function (cm) { return cm.moveV(1, "line"); },
- goPageUp: function (cm) { return cm.moveV(-1, "page"); },
- goPageDown: function (cm) { return cm.moveV(1, "page"); },
- goCharLeft: function (cm) { return cm.moveH(-1, "char"); },
- goCharRight: function (cm) { return cm.moveH(1, "char"); },
- goColumnLeft: function (cm) { return cm.moveH(-1, "column"); },
- goColumnRight: function (cm) { return cm.moveH(1, "column"); },
- goWordLeft: function (cm) { return cm.moveH(-1, "word"); },
- goGroupRight: function (cm) { return cm.moveH(1, "group"); },
- goGroupLeft: function (cm) { return cm.moveH(-1, "group"); },
- goWordRight: function (cm) { return cm.moveH(1, "word"); },
- delCharBefore: function (cm) { return cm.deleteH(-1, "char"); },
- delCharAfter: function (cm) { return cm.deleteH(1, "char"); },
- delWordBefore: function (cm) { return cm.deleteH(-1, "word"); },
- delWordAfter: function (cm) { return cm.deleteH(1, "word"); },
- delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); },
- delGroupAfter: function (cm) { return cm.deleteH(1, "group"); },
- indentAuto: function (cm) { return cm.indentSelection("smart"); },
- indentMore: function (cm) { return cm.indentSelection("add"); },
- indentLess: function (cm) { return cm.indentSelection("subtract"); },
- insertTab: function (cm) { return cm.replaceSelection("\t"); },
- insertSoftTab: function (cm) {
- var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize
- for (var i = 0; i < ranges.length; i++) {
- var pos = ranges[i].from()
- var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize)
- spaces.push(spaceStr(tabSize - col % tabSize))
- }
- cm.replaceSelections(spaces)
- },
- defaultTab: function (cm) {
- if (cm.somethingSelected()) { cm.indentSelection("add") }
- else { cm.execCommand("insertTab") }
- },
- // Swap the two chars left and right of each selection's head.
- // Move cursor behind the two swapped characters afterwards.
- //
- // Doesn't consider line feeds a character.
- // Doesn't scan more than one line above to find a character.
- // Doesn't do anything on an empty line.
- // Doesn't do anything with non-empty selections.
- transposeChars: function (cm) { return runInOp(cm, function () {
- var ranges = cm.listSelections(), newSel = []
- for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) { continue }
- var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text
- if (line) {
- if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) }
- if (cur.ch > 0) {
- cur = new Pos(cur.line, cur.ch + 1)
- cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
- Pos(cur.line, cur.ch - 2), cur, "+transpose")
- } else if (cur.line > cm.doc.first) {
- var prev = getLine(cm.doc, cur.line - 1).text
- if (prev) {
- cur = new Pos(cur.line, 1)
- cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
- prev.charAt(prev.length - 1),
- Pos(cur.line - 1, prev.length - 1), cur, "+transpose")
- }
- }
- }
- newSel.push(new Range(cur, cur))
- }
- cm.setSelections(newSel)
- }); },
- newlineAndIndent: function (cm) { return runInOp(cm, function () {
- var sels = cm.listSelections()
- for (var i = sels.length - 1; i >= 0; i--)
- { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") }
- sels = cm.listSelections()
- for (var i$1 = 0; i$1 < sels.length; i$1++)
- { cm.indentLine(sels[i$1].from().line, null, true) }
- ensureCursorVisible(cm)
- }); },
- openLine: function (cm) { return cm.replaceSelection("\n", "start"); },
- toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }
-}
-
-
-function lineStart(cm, lineN) {
- var line = getLine(cm.doc, lineN)
- var visual = visualLine(line)
- if (visual != line) { lineN = lineNo(visual) }
- return endOfLine(true, cm, visual, lineN, 1)
-}
-function lineEnd(cm, lineN) {
- var line = getLine(cm.doc, lineN)
- var visual = visualLineEnd(line)
- if (visual != line) { lineN = lineNo(visual) }
- return endOfLine(true, cm, line, lineN, -1)
-}
-function lineStartSmart(cm, pos) {
- var start = lineStart(cm, pos.line)
- var line = getLine(cm.doc, start.line)
- var order = getOrder(line, cm.doc.direction)
- if (!order || order[0].level == 0) {
- var firstNonWS = Math.max(0, line.text.search(/\S/))
- var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch
- return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
- }
- return start
-}
-
-// Run a handler that was bound to a key.
-function doHandleBinding(cm, bound, dropShift) {
- if (typeof bound == "string") {
- bound = commands[bound]
- if (!bound) { return false }
- }
- // Ensure previous input has been read, so that the handler sees a
- // consistent view of the document
- cm.display.input.ensurePolled()
- var prevShift = cm.display.shift, done = false
- try {
- if (cm.isReadOnly()) { cm.state.suppressEdits = true }
- if (dropShift) { cm.display.shift = false }
- done = bound(cm) != Pass
- } finally {
- cm.display.shift = prevShift
- cm.state.suppressEdits = false
- }
- return done
-}
-
-function lookupKeyForEditor(cm, name, handle) {
- for (var i = 0; i < cm.state.keyMaps.length; i++) {
- var result = lookupKey(name, cm.state.keyMaps[i], handle, cm)
- if (result) { return result }
- }
- return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))
- || lookupKey(name, cm.options.keyMap, handle, cm)
-}
-
-// Note that, despite the name, this function is also used to check
-// for bound mouse clicks.
-
-var stopSeq = new Delayed
-function dispatchKey(cm, name, e, handle) {
- var seq = cm.state.keySeq
- if (seq) {
- if (isModifierKey(name)) { return "handled" }
- stopSeq.set(50, function () {
- if (cm.state.keySeq == seq) {
- cm.state.keySeq = null
- cm.display.input.reset()
- }
- })
- name = seq + " " + name
- }
- var result = lookupKeyForEditor(cm, name, handle)
-
- if (result == "multi")
- { cm.state.keySeq = name }
- if (result == "handled")
- { signalLater(cm, "keyHandled", cm, name, e) }
-
- if (result == "handled" || result == "multi") {
- e_preventDefault(e)
- restartBlink(cm)
- }
-
- if (seq && !result && /\'$/.test(name)) {
- e_preventDefault(e)
- return true
- }
- return !!result
-}
-
-// Handle a key from the keydown event.
-function handleKeyBinding(cm, e) {
- var name = keyName(e, true)
- if (!name) { return false }
-
- if (e.shiftKey && !cm.state.keySeq) {
- // First try to resolve full name (including 'Shift-'). Failing
- // that, see if there is a cursor-motion command (starting with
- // 'go') bound to the keyname without 'Shift-'.
- return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); })
- || dispatchKey(cm, name, e, function (b) {
- if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
- { return doHandleBinding(cm, b) }
- })
- } else {
- return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })
- }
-}
-
-// Handle a key from the keypress event
-function handleCharBinding(cm, e, ch) {
- return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); })
-}
-
-var lastStoppedKey = null
-function onKeyDown(e) {
- var cm = this
- cm.curOp.focus = activeElt()
- if (signalDOMEvent(cm, e)) { return }
- // IE does strange things with escape.
- if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false }
- var code = e.keyCode
- cm.display.shift = code == 16 || e.shiftKey
- var handled = handleKeyBinding(cm, e)
- if (presto) {
- lastStoppedKey = handled ? code : null
- // Opera has no cut event... we try to at least catch the key combo
- if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
- { cm.replaceSelection("", null, "cut") }
- }
-
- // Turn mouse into crosshair when Alt is held on Mac.
- if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
- { showCrossHair(cm) }
-}
-
-function showCrossHair(cm) {
- var lineDiv = cm.display.lineDiv
- addClass(lineDiv, "CodeMirror-crosshair")
-
- function up(e) {
- if (e.keyCode == 18 || !e.altKey) {
- rmClass(lineDiv, "CodeMirror-crosshair")
- off(document, "keyup", up)
- off(document, "mouseover", up)
- }
- }
- on(document, "keyup", up)
- on(document, "mouseover", up)
-}
-
-function onKeyUp(e) {
- if (e.keyCode == 16) { this.doc.sel.shift = false }
- signalDOMEvent(this, e)
-}
-
-function onKeyPress(e) {
- var cm = this
- if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }
- var keyCode = e.keyCode, charCode = e.charCode
- if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}
- if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }
- var ch = String.fromCharCode(charCode == null ? keyCode : charCode)
- // Some browsers fire keypress events for backspace
- if (ch == "\x08") { return }
- if (handleCharBinding(cm, e, ch)) { return }
- cm.display.input.onKeyPress(e)
-}
-
-var DOUBLECLICK_DELAY = 400
-
-var PastClick = function(time, pos, button) {
- this.time = time
- this.pos = pos
- this.button = button
-};
-
-PastClick.prototype.compare = function (time, pos, button) {
- return this.time + DOUBLECLICK_DELAY > time &&
- cmp(pos, this.pos) == 0 && button == this.button
-};
-
-var lastClick;
-var lastDoubleClick;
-function clickRepeat(pos, button) {
- var now = +new Date
- if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {
- lastClick = lastDoubleClick = null
- return "triple"
- } else if (lastClick && lastClick.compare(now, pos, button)) {
- lastDoubleClick = new PastClick(now, pos, button)
- lastClick = null
- return "double"
- } else {
- lastClick = new PastClick(now, pos, button)
- lastDoubleClick = null
- return "single"
- }
-}
-
-// A mouse down can be a single click, double click, triple click,
-// start of selection drag, start of text drag, new cursor
-// (ctrl-click), rectangle drag (alt-drag), or xwin
-// middle-click-paste. Or it might be a click on something we should
-// not interfere with, such as a scrollbar or widget.
-function onMouseDown(e) {
- var cm = this, display = cm.display
- if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }
- display.input.ensurePolled()
- display.shift = e.shiftKey
-
- if (eventInWidget(display, e)) {
- if (!webkit) {
- // Briefly turn off draggability, to allow widgets to do
- // normal dragging things.
- display.scroller.draggable = false
- setTimeout(function () { return display.scroller.draggable = true; }, 100)
- }
- return
- }
- if (clickInGutter(cm, e)) { return }
- var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"
- window.focus()
-
- // #3261: make sure, that we're not starting a second selection
- if (button == 1 && cm.state.selectingText)
- { cm.state.selectingText(e) }
-
- if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }
-
- if (button == 1) {
- if (pos) { leftButtonDown(cm, pos, repeat, e) }
- else if (e_target(e) == display.scroller) { e_preventDefault(e) }
- } else if (button == 2) {
- if (pos) { extendSelection(cm.doc, pos) }
- setTimeout(function () { return display.input.focus(); }, 20)
- } else if (button == 3) {
- if (captureRightClick) { onContextMenu(cm, e) }
- else { delayBlurEvent(cm) }
- }
-}
-
-function handleMappedButton(cm, button, pos, repeat, event) {
- var name = "Click"
- if (repeat == "double") { name = "Double" + name }
- else if (repeat == "triple") { name = "Triple" + name }
- name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name
-
- return dispatchKey(cm, addModifierNames(name, event), event, function (bound) {
- if (typeof bound == "string") { bound = commands[bound] }
- if (!bound) { return false }
- var done = false
- try {
- if (cm.isReadOnly()) { cm.state.suppressEdits = true }
- done = bound(cm, pos) != Pass
- } finally {
- cm.state.suppressEdits = false
- }
- return done
- })
-}
-
-function configureMouse(cm, repeat, event) {
- var option = cm.getOption("configureMouse")
- var value = option ? option(cm, repeat, event) : {}
- if (value.unit == null) {
- var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey
- value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"
- }
- if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey }
- if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey }
- if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) }
- return value
-}
-
-function leftButtonDown(cm, pos, repeat, event) {
- if (ie) { setTimeout(bind(ensureFocus, cm), 0) }
- else { cm.curOp.focus = activeElt() }
-
- var behavior = configureMouse(cm, repeat, event)
-
- var sel = cm.doc.sel, contained
- if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
- repeat == "single" && (contained = sel.contains(pos)) > -1 &&
- (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&
- (cmp(contained.to(), pos) > 0 || pos.xRel < 0))
- { leftButtonStartDrag(cm, event, pos, behavior) }
- else
- { leftButtonSelect(cm, event, pos, behavior) }
-}
-
-// Start a text drag. When it ends, see if any dragging actually
-// happen, and treat as a click if it didn't.
-function leftButtonStartDrag(cm, event, pos, behavior) {
- var display = cm.display, moved = false
- var dragEnd = operation(cm, function (e) {
- if (webkit) { display.scroller.draggable = false }
- cm.state.draggingText = false
- off(document, "mouseup", dragEnd)
- off(document, "mousemove", mouseMove)
- off(display.scroller, "dragstart", dragStart)
- off(display.scroller, "drop", dragEnd)
- if (!moved) {
- e_preventDefault(e)
- if (!behavior.addNew)
- { extendSelection(cm.doc, pos, null, null, behavior.extend) }
- // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
- if (webkit || ie && ie_version == 9)
- { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) }
- else
- { display.input.focus() }
- }
- })
- var mouseMove = function(e2) {
- moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10
- }
- var dragStart = function () { return moved = true; }
- // Let the drag handler handle this.
- if (webkit) { display.scroller.draggable = true }
- cm.state.draggingText = dragEnd
- dragEnd.copy = !behavior.moveOnDrag
- // IE's approach to draggable
- if (display.scroller.dragDrop) { display.scroller.dragDrop() }
- on(document, "mouseup", dragEnd)
- on(document, "mousemove", mouseMove)
- on(display.scroller, "dragstart", dragStart)
- on(display.scroller, "drop", dragEnd)
-
- delayBlurEvent(cm)
- setTimeout(function () { return display.input.focus(); }, 20)
-}
-
-function rangeForUnit(cm, pos, unit) {
- if (unit == "char") { return new Range(pos, pos) }
- if (unit == "word") { return cm.findWordAt(pos) }
- if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }
- var result = unit(cm, pos)
- return new Range(result.from, result.to)
-}
-
-// Normal selection, as opposed to text dragging.
-function leftButtonSelect(cm, event, start, behavior) {
- var display = cm.display, doc = cm.doc
- e_preventDefault(event)
-
- var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges
- if (behavior.addNew && !behavior.extend) {
- ourIndex = doc.sel.contains(start)
- if (ourIndex > -1)
- { ourRange = ranges[ourIndex] }
- else
- { ourRange = new Range(start, start) }
- } else {
- ourRange = doc.sel.primary()
- ourIndex = doc.sel.primIndex
- }
-
- if (behavior.unit == "rectangle") {
- if (!behavior.addNew) { ourRange = new Range(start, start) }
- start = posFromMouse(cm, event, true, true)
- ourIndex = -1
- } else {
- var range = rangeForUnit(cm, start, behavior.unit)
- if (behavior.extend)
- { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) }
- else
- { ourRange = range }
- }
-
- if (!behavior.addNew) {
- ourIndex = 0
- setSelection(doc, new Selection([ourRange], 0), sel_mouse)
- startSel = doc.sel
- } else if (ourIndex == -1) {
- ourIndex = ranges.length
- setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
- {scroll: false, origin: "*mouse"})
- } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) {
- setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
- {scroll: false, origin: "*mouse"})
- startSel = doc.sel
- } else {
- replaceOneSelection(doc, ourIndex, ourRange, sel_mouse)
- }
-
- var lastPos = start
- function extendTo(pos) {
- if (cmp(lastPos, pos) == 0) { return }
- lastPos = pos
-
- if (behavior.unit == "rectangle") {
- var ranges = [], tabSize = cm.options.tabSize
- var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize)
- var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize)
- var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol)
- for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
- line <= end; line++) {
- var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize)
- if (left == right)
- { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) }
- else if (text.length > leftPos)
- { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) }
- }
- if (!ranges.length) { ranges.push(new Range(start, start)) }
- setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
- {origin: "*mouse", scroll: false})
- cm.scrollIntoView(pos)
- } else {
- var oldRange = ourRange
- var range = rangeForUnit(cm, pos, behavior.unit)
- var anchor = oldRange.anchor, head
- if (cmp(range.anchor, anchor) > 0) {
- head = range.head
- anchor = minPos(oldRange.from(), range.anchor)
- } else {
- head = range.anchor
- anchor = maxPos(oldRange.to(), range.head)
- }
- var ranges$1 = startSel.ranges.slice(0)
- ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head)
- setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse)
- }
- }
-
- var editorSize = display.wrapper.getBoundingClientRect()
- // Used to ensure timeout re-tries don't fire when another extend
- // happened in the meantime (clearTimeout isn't reliable -- at
- // least on Chrome, the timeouts still happen even when cleared,
- // if the clear happens after their scheduled firing time).
- var counter = 0
-
- function extend(e) {
- var curCount = ++counter
- var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle")
- if (!cur) { return }
- if (cmp(cur, lastPos) != 0) {
- cm.curOp.focus = activeElt()
- extendTo(cur)
- var visible = visibleLines(display, doc)
- if (cur.line >= visible.to || cur.line < visible.from)
- { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) }
- } else {
- var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0
- if (outside) { setTimeout(operation(cm, function () {
- if (counter != curCount) { return }
- display.scroller.scrollTop += outside
- extend(e)
- }), 50) }
- }
- }
-
- function done(e) {
- cm.state.selectingText = false
- counter = Infinity
- e_preventDefault(e)
- display.input.focus()
- off(document, "mousemove", move)
- off(document, "mouseup", up)
- doc.history.lastSelOrigin = null
- }
-
- var move = operation(cm, function (e) {
- if (!e_button(e)) { done(e) }
- else { extend(e) }
- })
- var up = operation(cm, done)
- cm.state.selectingText = up
- on(document, "mousemove", move)
- on(document, "mouseup", up)
-}
-
-
-// Determines whether an event happened in the gutter, and fires the
-// handlers for the corresponding event.
-function gutterEvent(cm, e, type, prevent) {
- var mX, mY
- try { mX = e.clientX; mY = e.clientY }
- catch(e) { return false }
- if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }
- if (prevent) { e_preventDefault(e) }
-
- var display = cm.display
- var lineBox = display.lineDiv.getBoundingClientRect()
-
- if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }
- mY -= lineBox.top - display.viewOffset
-
- for (var i = 0; i < cm.options.gutters.length; ++i) {
- var g = display.gutters.childNodes[i]
- if (g && g.getBoundingClientRect().right >= mX) {
- var line = lineAtHeight(cm.doc, mY)
- var gutter = cm.options.gutters[i]
- signal(cm, type, cm, line, gutter, e)
- return e_defaultPrevented(e)
- }
- }
-}
-
-function clickInGutter(cm, e) {
- return gutterEvent(cm, e, "gutterClick", true)
-}
-
-// CONTEXT MENU HANDLING
-
-// To make the context menu work, we need to briefly unhide the
-// textarea (making it as unobtrusive as possible) to let the
-// right-click take effect on it.
-function onContextMenu(cm, e) {
- if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }
- if (signalDOMEvent(cm, e, "contextmenu")) { return }
- cm.display.input.onContextMenu(e)
-}
-
-function contextMenuInGutter(cm, e) {
- if (!hasHandler(cm, "gutterContextMenu")) { return false }
- return gutterEvent(cm, e, "gutterContextMenu", false)
-}
-
-function themeChanged(cm) {
- cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
- cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-")
- clearCaches(cm)
-}
-
-var Init = {toString: function(){return "CodeMirror.Init"}}
-
-var defaults = {}
-var optionHandlers = {}
-
-function defineOptions(CodeMirror) {
- var optionHandlers = CodeMirror.optionHandlers
-
- function option(name, deflt, handle, notOnInit) {
- CodeMirror.defaults[name] = deflt
- if (handle) { optionHandlers[name] =
- notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle }
- }
-
- CodeMirror.defineOption = option
-
- // Passed to option handlers when there is no old value.
- CodeMirror.Init = Init
-
- // These two are, on init, called from the constructor because they
- // have to be initialized before the editor can start at all.
- option("value", "", function (cm, val) { return cm.setValue(val); }, true)
- option("mode", null, function (cm, val) {
- cm.doc.modeOption = val
- loadMode(cm)
- }, true)
-
- option("indentUnit", 2, loadMode, true)
- option("indentWithTabs", false)
- option("smartIndent", true)
- option("tabSize", 4, function (cm) {
- resetModeState(cm)
- clearCaches(cm)
- regChange(cm)
- }, true)
- option("lineSeparator", null, function (cm, val) {
- cm.doc.lineSep = val
- if (!val) { return }
- var newBreaks = [], lineNo = cm.doc.first
- cm.doc.iter(function (line) {
- for (var pos = 0;;) {
- var found = line.text.indexOf(val, pos)
- if (found == -1) { break }
- pos = found + val.length
- newBreaks.push(Pos(lineNo, found))
- }
- lineNo++
- })
- for (var i = newBreaks.length - 1; i >= 0; i--)
- { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) }
- })
- option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) {
- cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g")
- if (old != Init) { cm.refresh() }
- })
- option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true)
- option("electricChars", true)
- option("inputStyle", mobile ? "contenteditable" : "textarea", function () {
- throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME
- }, true)
- option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true)
- option("rtlMoveVisually", !windows)
- option("wholeLineUpdateBefore", true)
-
- option("theme", "default", function (cm) {
- themeChanged(cm)
- guttersChanged(cm)
- }, true)
- option("keyMap", "default", function (cm, val, old) {
- var next = getKeyMap(val)
- var prev = old != Init && getKeyMap(old)
- if (prev && prev.detach) { prev.detach(cm, next) }
- if (next.attach) { next.attach(cm, prev || null) }
- })
- option("extraKeys", null)
- option("configureMouse", null)
-
- option("lineWrapping", false, wrappingChanged, true)
- option("gutters", [], function (cm) {
- setGuttersForLineNumbers(cm.options)
- guttersChanged(cm)
- }, true)
- option("fixedGutter", true, function (cm, val) {
- cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"
- cm.refresh()
- }, true)
- option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true)
- option("scrollbarStyle", "native", function (cm) {
- initScrollbars(cm)
- updateScrollbars(cm)
- cm.display.scrollbars.setScrollTop(cm.doc.scrollTop)
- cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft)
- }, true)
- option("lineNumbers", false, function (cm) {
- setGuttersForLineNumbers(cm.options)
- guttersChanged(cm)
- }, true)
- option("firstLineNumber", 1, guttersChanged, true)
- option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true)
- option("showCursorWhenSelecting", false, updateSelection, true)
-
- option("resetSelectionOnContextMenu", true)
- option("lineWiseCopyCut", true)
- option("pasteLinesPerSelection", true)
-
- option("readOnly", false, function (cm, val) {
- if (val == "nocursor") {
- onBlur(cm)
- cm.display.input.blur()
- }
- cm.display.input.readOnlyChanged(val)
- })
- option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true)
- option("dragDrop", true, dragDropChanged)
- option("allowDropFileTypes", null)
-
- option("cursorBlinkRate", 530)
- option("cursorScrollMargin", 0)
- option("cursorHeight", 1, updateSelection, true)
- option("singleCursorHeightPerLine", true, updateSelection, true)
- option("workTime", 100)
- option("workDelay", 100)
- option("flattenSpans", true, resetModeState, true)
- option("addModeClass", false, resetModeState, true)
- option("pollInterval", 100)
- option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; })
- option("historyEventDelay", 1250)
- option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true)
- option("maxHighlightLength", 10000, resetModeState, true)
- option("moveInputWithCursor", true, function (cm, val) {
- if (!val) { cm.display.input.resetPosition() }
- })
-
- option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; })
- option("autofocus", null)
- option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true)
-}
-
-function guttersChanged(cm) {
- updateGutters(cm)
- regChange(cm)
- alignHorizontally(cm)
-}
-
-function dragDropChanged(cm, value, old) {
- var wasOn = old && old != Init
- if (!value != !wasOn) {
- var funcs = cm.display.dragFunctions
- var toggle = value ? on : off
- toggle(cm.display.scroller, "dragstart", funcs.start)
- toggle(cm.display.scroller, "dragenter", funcs.enter)
- toggle(cm.display.scroller, "dragover", funcs.over)
- toggle(cm.display.scroller, "dragleave", funcs.leave)
- toggle(cm.display.scroller, "drop", funcs.drop)
- }
-}
-
-function wrappingChanged(cm) {
- if (cm.options.lineWrapping) {
- addClass(cm.display.wrapper, "CodeMirror-wrap")
- cm.display.sizer.style.minWidth = ""
- cm.display.sizerWidth = null
- } else {
- rmClass(cm.display.wrapper, "CodeMirror-wrap")
- findMaxLine(cm)
- }
- estimateLineHeights(cm)
- regChange(cm)
- clearCaches(cm)
- setTimeout(function () { return updateScrollbars(cm); }, 100)
-}
-
-// A CodeMirror instance represents an editor. This is the object
-// that user code is usually dealing with.
-
-function CodeMirror(place, options) {
- var this$1 = this;
-
- if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }
-
- this.options = options = options ? copyObj(options) : {}
- // Determine effective options based on given values and defaults.
- copyObj(defaults, options, false)
- setGuttersForLineNumbers(options)
-
- var doc = options.value
- if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) }
- this.doc = doc
-
- var input = new CodeMirror.inputStyles[options.inputStyle](this)
- var display = this.display = new Display(place, doc, input)
- display.wrapper.CodeMirror = this
- updateGutters(this)
- themeChanged(this)
- if (options.lineWrapping)
- { this.display.wrapper.className += " CodeMirror-wrap" }
- initScrollbars(this)
-
- this.state = {
- keyMaps: [], // stores maps added by addKeyMap
- overlays: [], // highlighting overlays, as added by addOverlay
- modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
- overwrite: false,
- delayingBlurEvent: false,
- focused: false,
- suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
- pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
- selectingText: false,
- draggingText: false,
- highlight: new Delayed(), // stores highlight worker timeout
- keySeq: null, // Unfinished key sequence
- specialChars: null
- }
-
- if (options.autofocus && !mobile) { display.input.focus() }
-
- // Override magic textarea content restore that IE sometimes does
- // on our hidden textarea on reload
- if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) }
-
- registerEventHandlers(this)
- ensureGlobalHandlers()
-
- startOperation(this)
- this.curOp.forceUpdate = true
- attachDoc(this, doc)
-
- if ((options.autofocus && !mobile) || this.hasFocus())
- { setTimeout(bind(onFocus, this), 20) }
- else
- { onBlur(this) }
-
- for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))
- { optionHandlers[opt](this$1, options[opt], Init) } }
- maybeUpdateLineNumberWidth(this)
- if (options.finishInit) { options.finishInit(this) }
- for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) }
- endOperation(this)
- // Suppress optimizelegibility in Webkit, since it breaks text
- // measuring on line wrapping boundaries.
- if (webkit && options.lineWrapping &&
- getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
- { display.lineDiv.style.textRendering = "auto" }
-}
-
-// The default configuration options.
-CodeMirror.defaults = defaults
-// Functions to run when options are changed.
-CodeMirror.optionHandlers = optionHandlers
-
-// Attach the necessary event handlers when initializing the editor
-function registerEventHandlers(cm) {
- var d = cm.display
- on(d.scroller, "mousedown", operation(cm, onMouseDown))
- // Older IE's will not fire a second mousedown for a double click
- if (ie && ie_version < 11)
- { on(d.scroller, "dblclick", operation(cm, function (e) {
- if (signalDOMEvent(cm, e)) { return }
- var pos = posFromMouse(cm, e)
- if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }
- e_preventDefault(e)
- var word = cm.findWordAt(pos)
- extendSelection(cm.doc, word.anchor, word.head)
- })) }
- else
- { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) }
- // Some browsers fire contextmenu *after* opening the menu, at
- // which point we can't mess with it anymore. Context menu is
- // handled in onMouseDown for these browsers.
- if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) }
-
- // Used to suppress mouse event handling when a touch happens
- var touchFinished, prevTouch = {end: 0}
- function finishTouch() {
- if (d.activeTouch) {
- touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000)
- prevTouch = d.activeTouch
- prevTouch.end = +new Date
- }
- }
- function isMouseLikeTouchEvent(e) {
- if (e.touches.length != 1) { return false }
- var touch = e.touches[0]
- return touch.radiusX <= 1 && touch.radiusY <= 1
- }
- function farAway(touch, other) {
- if (other.left == null) { return true }
- var dx = other.left - touch.left, dy = other.top - touch.top
- return dx * dx + dy * dy > 20 * 20
- }
- on(d.scroller, "touchstart", function (e) {
- if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
- d.input.ensurePolled()
- clearTimeout(touchFinished)
- var now = +new Date
- d.activeTouch = {start: now, moved: false,
- prev: now - prevTouch.end <= 300 ? prevTouch : null}
- if (e.touches.length == 1) {
- d.activeTouch.left = e.touches[0].pageX
- d.activeTouch.top = e.touches[0].pageY
- }
- }
- })
- on(d.scroller, "touchmove", function () {
- if (d.activeTouch) { d.activeTouch.moved = true }
- })
- on(d.scroller, "touchend", function (e) {
- var touch = d.activeTouch
- if (touch && !eventInWidget(d, e) && touch.left != null &&
- !touch.moved && new Date - touch.start < 300) {
- var pos = cm.coordsChar(d.activeTouch, "page"), range
- if (!touch.prev || farAway(touch, touch.prev)) // Single tap
- { range = new Range(pos, pos) }
- else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
- { range = cm.findWordAt(pos) }
- else // Triple tap
- { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }
- cm.setSelection(range.anchor, range.head)
- cm.focus()
- e_preventDefault(e)
- }
- finishTouch()
- })
- on(d.scroller, "touchcancel", finishTouch)
-
- // Sync scrolling between fake scrollbars and real scrollable
- // area, ensure viewport is updated when scrolling.
- on(d.scroller, "scroll", function () {
- if (d.scroller.clientHeight) {
- updateScrollTop(cm, d.scroller.scrollTop)
- setScrollLeft(cm, d.scroller.scrollLeft, true)
- signal(cm, "scroll", cm)
- }
- })
-
- // Listen to wheel events in order to try and update the viewport on time.
- on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); })
- on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); })
-
- // Prevent wrapper from ever scrolling
- on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; })
-
- d.dragFunctions = {
- enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }},
- over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }},
- start: function (e) { return onDragStart(cm, e); },
- drop: operation(cm, onDrop),
- leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }}
- }
-
- var inp = d.input.getField()
- on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); })
- on(inp, "keydown", operation(cm, onKeyDown))
- on(inp, "keypress", operation(cm, onKeyPress))
- on(inp, "focus", function (e) { return onFocus(cm, e); })
- on(inp, "blur", function (e) { return onBlur(cm, e); })
-}
-
-var initHooks = []
-CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }
-
-// Indent the given line. The how parameter can be "smart",
-// "add"/null, "subtract", or "prev". When aggressive is false
-// (typically set to true for forced single-line indents), empty
-// lines are not indented, and places where the mode returns Pass
-// are left alone.
-function indentLine(cm, n, how, aggressive) {
- var doc = cm.doc, state
- if (how == null) { how = "add" }
- if (how == "smart") {
- // Fall back to "prev" when the mode doesn't have an indentation
- // method.
- if (!doc.mode.indent) { how = "prev" }
- else { state = getContextBefore(cm, n).state }
- }
-
- var tabSize = cm.options.tabSize
- var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize)
- if (line.stateAfter) { line.stateAfter = null }
- var curSpaceString = line.text.match(/^\s*/)[0], indentation
- if (!aggressive && !/\S/.test(line.text)) {
- indentation = 0
- how = "not"
- } else if (how == "smart") {
- indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text)
- if (indentation == Pass || indentation > 150) {
- if (!aggressive) { return }
- how = "prev"
- }
- }
- if (how == "prev") {
- if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) }
- else { indentation = 0 }
- } else if (how == "add") {
- indentation = curSpace + cm.options.indentUnit
- } else if (how == "subtract") {
- indentation = curSpace - cm.options.indentUnit
- } else if (typeof how == "number") {
- indentation = curSpace + how
- }
- indentation = Math.max(0, indentation)
-
- var indentString = "", pos = 0
- if (cm.options.indentWithTabs)
- { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} }
- if (pos < indentation) { indentString += spaceStr(indentation - pos) }
-
- if (indentString != curSpaceString) {
- replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input")
- line.stateAfter = null
- return true
- } else {
- // Ensure that, if the cursor was in the whitespace at the start
- // of the line, it is moved to the end of that space.
- for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {
- var range = doc.sel.ranges[i$1]
- if (range.head.line == n && range.head.ch < curSpaceString.length) {
- var pos$1 = Pos(n, curSpaceString.length)
- replaceOneSelection(doc, i$1, new Range(pos$1, pos$1))
- break
- }
- }
- }
-}
-
-// This will be set to a {lineWise: bool, text: [string]} object, so
-// that, when pasting, we know what kind of selections the copied
-// text was made out of.
-var lastCopied = null
-
-function setLastCopied(newLastCopied) {
- lastCopied = newLastCopied
-}
-
-function applyTextInput(cm, inserted, deleted, sel, origin) {
- var doc = cm.doc
- cm.display.shift = false
- if (!sel) { sel = doc.sel }
-
- var paste = cm.state.pasteIncoming || origin == "paste"
- var textLines = splitLinesAuto(inserted), multiPaste = null
- // When pasing N lines into N selections, insert one line per selection
- if (paste && sel.ranges.length > 1) {
- if (lastCopied && lastCopied.text.join("\n") == inserted) {
- if (sel.ranges.length % lastCopied.text.length == 0) {
- multiPaste = []
- for (var i = 0; i < lastCopied.text.length; i++)
- { multiPaste.push(doc.splitLines(lastCopied.text[i])) }
- }
- } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {
- multiPaste = map(textLines, function (l) { return [l]; })
- }
- }
-
- var updateInput
- // Normal behavior is to insert the new text into every selection
- for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {
- var range = sel.ranges[i$1]
- var from = range.from(), to = range.to()
- if (range.empty()) {
- if (deleted && deleted > 0) // Handle deletion
- { from = Pos(from.line, from.ch - deleted) }
- else if (cm.state.overwrite && !paste) // Handle overwrite
- { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) }
- else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
- { from = to = Pos(from.line, 0) }
- }
- updateInput = cm.curOp.updateInput
- var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,
- origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}
- makeChange(cm.doc, changeEvent)
- signalLater(cm, "inputRead", cm, changeEvent)
- }
- if (inserted && !paste)
- { triggerElectric(cm, inserted) }
-
- ensureCursorVisible(cm)
- cm.curOp.updateInput = updateInput
- cm.curOp.typing = true
- cm.state.pasteIncoming = cm.state.cutIncoming = false
-}
-
-function handlePaste(e, cm) {
- var pasted = e.clipboardData && e.clipboardData.getData("Text")
- if (pasted) {
- e.preventDefault()
- if (!cm.isReadOnly() && !cm.options.disableInput)
- { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) }
- return true
- }
-}
-
-function triggerElectric(cm, inserted) {
- // When an 'electric' character is inserted, immediately trigger a reindent
- if (!cm.options.electricChars || !cm.options.smartIndent) { return }
- var sel = cm.doc.sel
-
- for (var i = sel.ranges.length - 1; i >= 0; i--) {
- var range = sel.ranges[i]
- if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue }
- var mode = cm.getModeAt(range.head)
- var indented = false
- if (mode.electricChars) {
- for (var j = 0; j < mode.electricChars.length; j++)
- { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
- indented = indentLine(cm, range.head.line, "smart")
- break
- } }
- } else if (mode.electricInput) {
- if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))
- { indented = indentLine(cm, range.head.line, "smart") }
- }
- if (indented) { signalLater(cm, "electricInput", cm, range.head.line) }
- }
-}
-
-function copyableRanges(cm) {
- var text = [], ranges = []
- for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
- var line = cm.doc.sel.ranges[i].head.line
- var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}
- ranges.push(lineRange)
- text.push(cm.getRange(lineRange.anchor, lineRange.head))
- }
- return {text: text, ranges: ranges}
-}
-
-function disableBrowserMagic(field, spellcheck) {
- field.setAttribute("autocorrect", "off")
- field.setAttribute("autocapitalize", "off")
- field.setAttribute("spellcheck", !!spellcheck)
-}
-
-function hiddenTextarea() {
- var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none")
- var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;")
- // The textarea is kept positioned near the cursor to prevent the
- // fact that it'll be scrolled into view on input from scrolling
- // our fake cursor out of view. On webkit, when wrap=off, paste is
- // very slow. So make the area wide instead.
- if (webkit) { te.style.width = "1000px" }
- else { te.setAttribute("wrap", "off") }
- // If border: 0; -- iOS fails to open keyboard (issue #1287)
- if (ios) { te.style.border = "1px solid black" }
- disableBrowserMagic(te)
- return div
-}
-
-// The publicly visible API. Note that methodOp(f) means
-// 'wrap f in an operation, performed on its `this` parameter'.
-
-// This is not the complete set of editor methods. Most of the
-// methods defined on the Doc type are also injected into
-// CodeMirror.prototype, for backwards compatibility and
-// convenience.
-
-function addEditorMethods(CodeMirror) {
- var optionHandlers = CodeMirror.optionHandlers
-
- var helpers = CodeMirror.helpers = {}
-
- CodeMirror.prototype = {
- constructor: CodeMirror,
- focus: function(){window.focus(); this.display.input.focus()},
-
- setOption: function(option, value) {
- var options = this.options, old = options[option]
- if (options[option] == value && option != "mode") { return }
- options[option] = value
- if (optionHandlers.hasOwnProperty(option))
- { operation(this, optionHandlers[option])(this, value, old) }
- signal(this, "optionChange", this, option)
- },
-
- getOption: function(option) {return this.options[option]},
- getDoc: function() {return this.doc},
-
- addKeyMap: function(map, bottom) {
- this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map))
- },
- removeKeyMap: function(map) {
- var maps = this.state.keyMaps
- for (var i = 0; i < maps.length; ++i)
- { if (maps[i] == map || maps[i].name == map) {
- maps.splice(i, 1)
- return true
- } }
- },
-
- addOverlay: methodOp(function(spec, options) {
- var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec)
- if (mode.startState) { throw new Error("Overlays may not be stateful.") }
- insertSorted(this.state.overlays,
- {mode: mode, modeSpec: spec, opaque: options && options.opaque,
- priority: (options && options.priority) || 0},
- function (overlay) { return overlay.priority; })
- this.state.modeGen++
- regChange(this)
- }),
- removeOverlay: methodOp(function(spec) {
- var this$1 = this;
-
- var overlays = this.state.overlays
- for (var i = 0; i < overlays.length; ++i) {
- var cur = overlays[i].modeSpec
- if (cur == spec || typeof spec == "string" && cur.name == spec) {
- overlays.splice(i, 1)
- this$1.state.modeGen++
- regChange(this$1)
- return
- }
- }
- }),
-
- indentLine: methodOp(function(n, dir, aggressive) {
- if (typeof dir != "string" && typeof dir != "number") {
- if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" }
- else { dir = dir ? "add" : "subtract" }
- }
- if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) }
- }),
- indentSelection: methodOp(function(how) {
- var this$1 = this;
-
- var ranges = this.doc.sel.ranges, end = -1
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i]
- if (!range.empty()) {
- var from = range.from(), to = range.to()
- var start = Math.max(end, from.line)
- end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1
- for (var j = start; j < end; ++j)
- { indentLine(this$1, j, how) }
- var newRanges = this$1.doc.sel.ranges
- if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
- { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) }
- } else if (range.head.line > end) {
- indentLine(this$1, range.head.line, how, true)
- end = range.head.line
- if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) }
- }
- }
- }),
-
- // Fetch the parser token for a given character. Useful for hacks
- // that want to inspect the mode state (say, for completion).
- getTokenAt: function(pos, precise) {
- return takeToken(this, pos, precise)
- },
-
- getLineTokens: function(line, precise) {
- return takeToken(this, Pos(line), precise, true)
- },
-
- getTokenTypeAt: function(pos) {
- pos = clipPos(this.doc, pos)
- var styles = getLineStyles(this, getLine(this.doc, pos.line))
- var before = 0, after = (styles.length - 1) / 2, ch = pos.ch
- var type
- if (ch == 0) { type = styles[2] }
- else { for (;;) {
- var mid = (before + after) >> 1
- if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid }
- else if (styles[mid * 2 + 1] < ch) { before = mid + 1 }
- else { type = styles[mid * 2 + 2]; break }
- } }
- var cut = type ? type.indexOf("overlay ") : -1
- return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)
- },
-
- getModeAt: function(pos) {
- var mode = this.doc.mode
- if (!mode.innerMode) { return mode }
- return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode
- },
-
- getHelper: function(pos, type) {
- return this.getHelpers(pos, type)[0]
- },
-
- getHelpers: function(pos, type) {
- var this$1 = this;
-
- var found = []
- if (!helpers.hasOwnProperty(type)) { return found }
- var help = helpers[type], mode = this.getModeAt(pos)
- if (typeof mode[type] == "string") {
- if (help[mode[type]]) { found.push(help[mode[type]]) }
- } else if (mode[type]) {
- for (var i = 0; i < mode[type].length; i++) {
- var val = help[mode[type][i]]
- if (val) { found.push(val) }
- }
- } else if (mode.helperType && help[mode.helperType]) {
- found.push(help[mode.helperType])
- } else if (help[mode.name]) {
- found.push(help[mode.name])
- }
- for (var i$1 = 0; i$1 < help._global.length; i$1++) {
- var cur = help._global[i$1]
- if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1)
- { found.push(cur.val) }
- }
- return found
- },
-
- getStateAfter: function(line, precise) {
- var doc = this.doc
- line = clipLine(doc, line == null ? doc.first + doc.size - 1: line)
- return getContextBefore(this, line + 1, precise).state
- },
-
- cursorCoords: function(start, mode) {
- var pos, range = this.doc.sel.primary()
- if (start == null) { pos = range.head }
- else if (typeof start == "object") { pos = clipPos(this.doc, start) }
- else { pos = start ? range.from() : range.to() }
- return cursorCoords(this, pos, mode || "page")
- },
-
- charCoords: function(pos, mode) {
- return charCoords(this, clipPos(this.doc, pos), mode || "page")
- },
-
- coordsChar: function(coords, mode) {
- coords = fromCoordSystem(this, coords, mode || "page")
- return coordsChar(this, coords.left, coords.top)
- },
-
- lineAtHeight: function(height, mode) {
- height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top
- return lineAtHeight(this.doc, height + this.display.viewOffset)
- },
- heightAtLine: function(line, mode, includeWidgets) {
- var end = false, lineObj
- if (typeof line == "number") {
- var last = this.doc.first + this.doc.size - 1
- if (line < this.doc.first) { line = this.doc.first }
- else if (line > last) { line = last; end = true }
- lineObj = getLine(this.doc, line)
- } else {
- lineObj = line
- }
- return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top +
- (end ? this.doc.height - heightAtLine(lineObj) : 0)
- },
-
- defaultTextHeight: function() { return textHeight(this.display) },
- defaultCharWidth: function() { return charWidth(this.display) },
-
- getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},
-
- addWidget: function(pos, node, scroll, vert, horiz) {
- var display = this.display
- pos = cursorCoords(this, clipPos(this.doc, pos))
- var top = pos.bottom, left = pos.left
- node.style.position = "absolute"
- node.setAttribute("cm-ignore-events", "true")
- this.display.input.setUneditable(node)
- display.sizer.appendChild(node)
- if (vert == "over") {
- top = pos.top
- } else if (vert == "above" || vert == "near") {
- var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
- hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth)
- // Default to positioning above (if specified and possible); otherwise default to positioning below
- if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
- { top = pos.top - node.offsetHeight }
- else if (pos.bottom + node.offsetHeight <= vspace)
- { top = pos.bottom }
- if (left + node.offsetWidth > hspace)
- { left = hspace - node.offsetWidth }
- }
- node.style.top = top + "px"
- node.style.left = node.style.right = ""
- if (horiz == "right") {
- left = display.sizer.clientWidth - node.offsetWidth
- node.style.right = "0px"
- } else {
- if (horiz == "left") { left = 0 }
- else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 }
- node.style.left = left + "px"
- }
- if (scroll)
- { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) }
- },
-
- triggerOnKeyDown: methodOp(onKeyDown),
- triggerOnKeyPress: methodOp(onKeyPress),
- triggerOnKeyUp: onKeyUp,
- triggerOnMouseDown: methodOp(onMouseDown),
-
- execCommand: function(cmd) {
- if (commands.hasOwnProperty(cmd))
- { return commands[cmd].call(null, this) }
- },
-
- triggerElectric: methodOp(function(text) { triggerElectric(this, text) }),
-
- findPosH: function(from, amount, unit, visually) {
- var this$1 = this;
-
- var dir = 1
- if (amount < 0) { dir = -1; amount = -amount }
- var cur = clipPos(this.doc, from)
- for (var i = 0; i < amount; ++i) {
- cur = findPosH(this$1.doc, cur, dir, unit, visually)
- if (cur.hitSide) { break }
- }
- return cur
- },
-
- moveH: methodOp(function(dir, unit) {
- var this$1 = this;
-
- this.extendSelectionsBy(function (range) {
- if (this$1.display.shift || this$1.doc.extend || range.empty())
- { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) }
- else
- { return dir < 0 ? range.from() : range.to() }
- }, sel_move)
- }),
-
- deleteH: methodOp(function(dir, unit) {
- var sel = this.doc.sel, doc = this.doc
- if (sel.somethingSelected())
- { doc.replaceSelection("", null, "+delete") }
- else
- { deleteNearSelection(this, function (range) {
- var other = findPosH(doc, range.head, dir, unit, false)
- return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}
- }) }
- }),
-
- findPosV: function(from, amount, unit, goalColumn) {
- var this$1 = this;
-
- var dir = 1, x = goalColumn
- if (amount < 0) { dir = -1; amount = -amount }
- var cur = clipPos(this.doc, from)
- for (var i = 0; i < amount; ++i) {
- var coords = cursorCoords(this$1, cur, "div")
- if (x == null) { x = coords.left }
- else { coords.left = x }
- cur = findPosV(this$1, coords, dir, unit)
- if (cur.hitSide) { break }
- }
- return cur
- },
-
- moveV: methodOp(function(dir, unit) {
- var this$1 = this;
-
- var doc = this.doc, goals = []
- var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected()
- doc.extendSelectionsBy(function (range) {
- if (collapse)
- { return dir < 0 ? range.from() : range.to() }
- var headPos = cursorCoords(this$1, range.head, "div")
- if (range.goalColumn != null) { headPos.left = range.goalColumn }
- goals.push(headPos.left)
- var pos = findPosV(this$1, headPos, dir, unit)
- if (unit == "page" && range == doc.sel.primary())
- { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top) }
- return pos
- }, sel_move)
- if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)
- { doc.sel.ranges[i].goalColumn = goals[i] } }
- }),
-
- // Find the word at the given position (as returned by coordsChar).
- findWordAt: function(pos) {
- var doc = this.doc, line = getLine(doc, pos.line).text
- var start = pos.ch, end = pos.ch
- if (line) {
- var helper = this.getHelper(pos, "wordChars")
- if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end }
- var startChar = line.charAt(start)
- var check = isWordChar(startChar, helper)
- ? function (ch) { return isWordChar(ch, helper); }
- : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); }
- : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }
- while (start > 0 && check(line.charAt(start - 1))) { --start }
- while (end < line.length && check(line.charAt(end))) { ++end }
- }
- return new Range(Pos(pos.line, start), Pos(pos.line, end))
- },
-
- toggleOverwrite: function(value) {
- if (value != null && value == this.state.overwrite) { return }
- if (this.state.overwrite = !this.state.overwrite)
- { addClass(this.display.cursorDiv, "CodeMirror-overwrite") }
- else
- { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") }
-
- signal(this, "overwriteToggle", this, this.state.overwrite)
- },
- hasFocus: function() { return this.display.input.getField() == activeElt() },
- isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
-
- scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }),
- getScrollInfo: function() {
- var scroller = this.display.scroller
- return {left: scroller.scrollLeft, top: scroller.scrollTop,
- height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,
- width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,
- clientHeight: displayHeight(this), clientWidth: displayWidth(this)}
- },
-
- scrollIntoView: methodOp(function(range, margin) {
- if (range == null) {
- range = {from: this.doc.sel.primary().head, to: null}
- if (margin == null) { margin = this.options.cursorScrollMargin }
- } else if (typeof range == "number") {
- range = {from: Pos(range, 0), to: null}
- } else if (range.from == null) {
- range = {from: range, to: null}
- }
- if (!range.to) { range.to = range.from }
- range.margin = margin || 0
-
- if (range.from.line != null) {
- scrollToRange(this, range)
- } else {
- scrollToCoordsRange(this, range.from, range.to, range.margin)
- }
- }),
-
- setSize: methodOp(function(width, height) {
- var this$1 = this;
-
- var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }
- if (width != null) { this.display.wrapper.style.width = interpret(width) }
- if (height != null) { this.display.wrapper.style.height = interpret(height) }
- if (this.options.lineWrapping) { clearLineMeasurementCache(this) }
- var lineNo = this.display.viewFrom
- this.doc.iter(lineNo, this.display.viewTo, function (line) {
- if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)
- { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } }
- ++lineNo
- })
- this.curOp.forceUpdate = true
- signal(this, "refresh", this)
- }),
-
- operation: function(f){return runInOp(this, f)},
- startOperation: function(){return startOperation(this)},
- endOperation: function(){return endOperation(this)},
-
- refresh: methodOp(function() {
- var oldHeight = this.display.cachedTextHeight
- regChange(this)
- this.curOp.forceUpdate = true
- clearCaches(this)
- scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop)
- updateGutterSpace(this)
- if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
- { estimateLineHeights(this) }
- signal(this, "refresh", this)
- }),
-
- swapDoc: methodOp(function(doc) {
- var old = this.doc
- old.cm = null
- attachDoc(this, doc)
- clearCaches(this)
- this.display.input.reset()
- scrollToCoords(this, doc.scrollLeft, doc.scrollTop)
- this.curOp.forceScroll = true
- signalLater(this, "swapDoc", this, old)
- return old
- }),
-
- getInputField: function(){return this.display.input.getField()},
- getWrapperElement: function(){return this.display.wrapper},
- getScrollerElement: function(){return this.display.scroller},
- getGutterElement: function(){return this.display.gutters}
- }
- eventMixin(CodeMirror)
-
- CodeMirror.registerHelper = function(type, name, value) {
- if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} }
- helpers[type][name] = value
- }
- CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
- CodeMirror.registerHelper(type, name, value)
- helpers[type]._global.push({pred: predicate, val: value})
- }
-}
-
-// Used for horizontal relative motion. Dir is -1 or 1 (left or
-// right), unit can be "char", "column" (like char, but doesn't
-// cross line boundaries), "word" (across next word), or "group" (to
-// the start of next group of word or non-word-non-whitespace
-// chars). The visually param controls whether, in right-to-left
-// text, direction 1 means to move towards the next index in the
-// string, or towards the character to the right of the current
-// position. The resulting position will have a hitSide=true
-// property if it reached the end of the document.
-function findPosH(doc, pos, dir, unit, visually) {
- var oldPos = pos
- var origDir = dir
- var lineObj = getLine(doc, pos.line)
- function findNextLine() {
- var l = pos.line + dir
- if (l < doc.first || l >= doc.first + doc.size) { return false }
- pos = new Pos(l, pos.ch, pos.sticky)
- return lineObj = getLine(doc, l)
- }
- function moveOnce(boundToLine) {
- var next
- if (visually) {
- next = moveVisually(doc.cm, lineObj, pos, dir)
- } else {
- next = moveLogically(lineObj, pos, dir)
- }
- if (next == null) {
- if (!boundToLine && findNextLine())
- { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) }
- else
- { return false }
- } else {
- pos = next
- }
- return true
- }
-
- if (unit == "char") {
- moveOnce()
- } else if (unit == "column") {
- moveOnce(true)
- } else if (unit == "word" || unit == "group") {
- var sawType = null, group = unit == "group"
- var helper = doc.cm && doc.cm.getHelper(pos, "wordChars")
- for (var first = true;; first = false) {
- if (dir < 0 && !moveOnce(!first)) { break }
- var cur = lineObj.text.charAt(pos.ch) || "\n"
- var type = isWordChar(cur, helper) ? "w"
- : group && cur == "\n" ? "n"
- : !group || /\s/.test(cur) ? null
- : "p"
- if (group && !first && !type) { type = "s" }
- if (sawType && sawType != type) {
- if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"}
- break
- }
-
- if (type) { sawType = type }
- if (dir > 0 && !moveOnce(!first)) { break }
- }
- }
- var result = skipAtomic(doc, pos, oldPos, origDir, true)
- if (equalCursorPos(oldPos, result)) { result.hitSide = true }
- return result
-}
-
-// For relative vertical movement. Dir may be -1 or 1. Unit can be
-// "page" or "line". The resulting position will have a hitSide=true
-// property if it reached the end of the document.
-function findPosV(cm, pos, dir, unit) {
- var doc = cm.doc, x = pos.left, y
- if (unit == "page") {
- var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight)
- var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3)
- y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount
-
- } else if (unit == "line") {
- y = dir > 0 ? pos.bottom + 3 : pos.top - 3
- }
- var target
- for (;;) {
- target = coordsChar(cm, x, y)
- if (!target.outside) { break }
- if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }
- y += dir * 5
- }
- return target
-}
-
-// CONTENTEDITABLE INPUT STYLE
-
-var ContentEditableInput = function(cm) {
- this.cm = cm
- this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null
- this.polling = new Delayed()
- this.composing = null
- this.gracePeriod = false
- this.readDOMTimeout = null
-};
-
-ContentEditableInput.prototype.init = function (display) {
- var this$1 = this;
-
- var input = this, cm = input.cm
- var div = input.div = display.lineDiv
- disableBrowserMagic(div, cm.options.spellcheck)
-
- on(div, "paste", function (e) {
- if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
- // IE doesn't fire input events, so we schedule a read for the pasted content in this way
- if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20) }
- })
-
- on(div, "compositionstart", function (e) {
- this$1.composing = {data: e.data, done: false}
- })
- on(div, "compositionupdate", function (e) {
- if (!this$1.composing) { this$1.composing = {data: e.data, done: false} }
- })
- on(div, "compositionend", function (e) {
- if (this$1.composing) {
- if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() }
- this$1.composing.done = true
- }
- })
-
- on(div, "touchstart", function () { return input.forceCompositionEnd(); })
-
- on(div, "input", function () {
- if (!this$1.composing) { this$1.readFromDOMSoon() }
- })
-
- function onCopyCut(e) {
- if (signalDOMEvent(cm, e)) { return }
- if (cm.somethingSelected()) {
- setLastCopied({lineWise: false, text: cm.getSelections()})
- if (e.type == "cut") { cm.replaceSelection("", null, "cut") }
- } else if (!cm.options.lineWiseCopyCut) {
- return
- } else {
- var ranges = copyableRanges(cm)
- setLastCopied({lineWise: true, text: ranges.text})
- if (e.type == "cut") {
- cm.operation(function () {
- cm.setSelections(ranges.ranges, 0, sel_dontScroll)
- cm.replaceSelection("", null, "cut")
- })
- }
- }
- if (e.clipboardData) {
- e.clipboardData.clearData()
- var content = lastCopied.text.join("\n")
- // iOS exposes the clipboard API, but seems to discard content inserted into it
- e.clipboardData.setData("Text", content)
- if (e.clipboardData.getData("Text") == content) {
- e.preventDefault()
- return
- }
- }
- // Old-fashioned briefly-focus-a-textarea hack
- var kludge = hiddenTextarea(), te = kludge.firstChild
- cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
- te.value = lastCopied.text.join("\n")
- var hadFocus = document.activeElement
- selectInput(te)
- setTimeout(function () {
- cm.display.lineSpace.removeChild(kludge)
- hadFocus.focus()
- if (hadFocus == div) { input.showPrimarySelection() }
- }, 50)
- }
- on(div, "copy", onCopyCut)
- on(div, "cut", onCopyCut)
-};
-
-ContentEditableInput.prototype.prepareSelection = function () {
- var result = prepareSelection(this.cm, false)
- result.focus = this.cm.state.focused
- return result
-};
-
-ContentEditableInput.prototype.showSelection = function (info, takeFocus) {
- if (!info || !this.cm.display.view.length) { return }
- if (info.focus || takeFocus) { this.showPrimarySelection() }
- this.showMultipleSelections(info)
-};
-
-ContentEditableInput.prototype.showPrimarySelection = function () {
- var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary()
- var from = prim.from(), to = prim.to()
-
- if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {
- sel.removeAllRanges()
- return
- }
-
- var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
- var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset)
- if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
- cmp(minPos(curAnchor, curFocus), from) == 0 &&
- cmp(maxPos(curAnchor, curFocus), to) == 0)
- { return }
-
- var view = cm.display.view
- var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||
- {node: view[0].measure.map[2], offset: 0}
- var end = to.line < cm.display.viewTo && posToDOM(cm, to)
- if (!end) {
- var measure = view[view.length - 1].measure
- var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map
- end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}
- }
-
- if (!start || !end) {
- sel.removeAllRanges()
- return
- }
-
- var old = sel.rangeCount && sel.getRangeAt(0), rng
- try { rng = range(start.node, start.offset, end.offset, end.node) }
- catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
- if (rng) {
- if (!gecko && cm.state.focused) {
- sel.collapse(start.node, start.offset)
- if (!rng.collapsed) {
- sel.removeAllRanges()
- sel.addRange(rng)
- }
- } else {
- sel.removeAllRanges()
- sel.addRange(rng)
- }
- if (old && sel.anchorNode == null) { sel.addRange(old) }
- else if (gecko) { this.startGracePeriod() }
- }
- this.rememberSelection()
-};
-
-ContentEditableInput.prototype.startGracePeriod = function () {
- var this$1 = this;
-
- clearTimeout(this.gracePeriod)
- this.gracePeriod = setTimeout(function () {
- this$1.gracePeriod = false
- if (this$1.selectionChanged())
- { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) }
- }, 20)
-};
-
-ContentEditableInput.prototype.showMultipleSelections = function (info) {
- removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors)
- removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection)
-};
-
-ContentEditableInput.prototype.rememberSelection = function () {
- var sel = window.getSelection()
- this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset
- this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset
-};
-
-ContentEditableInput.prototype.selectionInEditor = function () {
- var sel = window.getSelection()
- if (!sel.rangeCount) { return false }
- var node = sel.getRangeAt(0).commonAncestorContainer
- return contains(this.div, node)
-};
-
-ContentEditableInput.prototype.focus = function () {
- if (this.cm.options.readOnly != "nocursor") {
- if (!this.selectionInEditor())
- { this.showSelection(this.prepareSelection(), true) }
- this.div.focus()
- }
-};
-ContentEditableInput.prototype.blur = function () { this.div.blur() };
-ContentEditableInput.prototype.getField = function () { return this.div };
-
-ContentEditableInput.prototype.supportsTouch = function () { return true };
-
-ContentEditableInput.prototype.receivedFocus = function () {
- var input = this
- if (this.selectionInEditor())
- { this.pollSelection() }
- else
- { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) }
-
- function poll() {
- if (input.cm.state.focused) {
- input.pollSelection()
- input.polling.set(input.cm.options.pollInterval, poll)
- }
- }
- this.polling.set(this.cm.options.pollInterval, poll)
-};
-
-ContentEditableInput.prototype.selectionChanged = function () {
- var sel = window.getSelection()
- return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
- sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
-};
-
-ContentEditableInput.prototype.pollSelection = function () {
- if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }
- var sel = window.getSelection(), cm = this.cm
- // On Android Chrome (version 56, at least), backspacing into an
- // uneditable block element will put the cursor in that element,
- // and then, because it's not editable, hide the virtual keyboard.
- // Because Android doesn't allow us to actually detect backspace
- // presses in a sane way, this code checks for when that happens
- // and simulates a backspace press in this case.
- if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) {
- this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs})
- this.blur()
- this.focus()
- return
- }
- if (this.composing) { return }
- this.rememberSelection()
- var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
- var head = domToPos(cm, sel.focusNode, sel.focusOffset)
- if (anchor && head) { runInOp(cm, function () {
- setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll)
- if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true }
- }) }
-};
-
-ContentEditableInput.prototype.pollContent = function () {
- if (this.readDOMTimeout != null) {
- clearTimeout(this.readDOMTimeout)
- this.readDOMTimeout = null
- }
-
- var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary()
- var from = sel.from(), to = sel.to()
- if (from.ch == 0 && from.line > cm.firstLine())
- { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) }
- if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())
- { to = Pos(to.line + 1, 0) }
- if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }
-
- var fromIndex, fromLine, fromNode
- if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
- fromLine = lineNo(display.view[0].line)
- fromNode = display.view[0].node
- } else {
- fromLine = lineNo(display.view[fromIndex].line)
- fromNode = display.view[fromIndex - 1].node.nextSibling
- }
- var toIndex = findViewIndex(cm, to.line)
- var toLine, toNode
- if (toIndex == display.view.length - 1) {
- toLine = display.viewTo - 1
- toNode = display.lineDiv.lastChild
- } else {
- toLine = lineNo(display.view[toIndex + 1].line) - 1
- toNode = display.view[toIndex + 1].node.previousSibling
- }
-
- if (!fromNode) { return false }
- var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine))
- var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length))
- while (newText.length > 1 && oldText.length > 1) {
- if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- }
- else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ }
- else { break }
- }
-
- var cutFront = 0, cutEnd = 0
- var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length)
- while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
- { ++cutFront }
- var newBot = lst(newText), oldBot = lst(oldText)
- var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
- oldBot.length - (oldText.length == 1 ? cutFront : 0))
- while (cutEnd < maxCutEnd &&
- newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
- { ++cutEnd }
- // Try to move start of change to start of selection if ambiguous
- if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {
- while (cutFront && cutFront > from.ch &&
- newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {
- cutFront--
- cutEnd++
- }
- }
-
- newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "")
- newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "")
-
- var chFrom = Pos(fromLine, cutFront)
- var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0)
- if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
- replaceRange(cm.doc, newText, chFrom, chTo, "+input")
- return true
- }
-};
-
-ContentEditableInput.prototype.ensurePolled = function () {
- this.forceCompositionEnd()
-};
-ContentEditableInput.prototype.reset = function () {
- this.forceCompositionEnd()
-};
-ContentEditableInput.prototype.forceCompositionEnd = function () {
- if (!this.composing) { return }
- clearTimeout(this.readDOMTimeout)
- this.composing = null
- this.updateFromDOM()
- this.div.blur()
- this.div.focus()
-};
-ContentEditableInput.prototype.readFromDOMSoon = function () {
- var this$1 = this;
-
- if (this.readDOMTimeout != null) { return }
- this.readDOMTimeout = setTimeout(function () {
- this$1.readDOMTimeout = null
- if (this$1.composing) {
- if (this$1.composing.done) { this$1.composing = null }
- else { return }
- }
- this$1.updateFromDOM()
- }, 80)
-};
-
-ContentEditableInput.prototype.updateFromDOM = function () {
- var this$1 = this;
-
- if (this.cm.isReadOnly() || !this.pollContent())
- { runInOp(this.cm, function () { return regChange(this$1.cm); }) }
-};
-
-ContentEditableInput.prototype.setUneditable = function (node) {
- node.contentEditable = "false"
-};
-
-ContentEditableInput.prototype.onKeyPress = function (e) {
- if (e.charCode == 0) { return }
- e.preventDefault()
- if (!this.cm.isReadOnly())
- { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) }
-};
-
-ContentEditableInput.prototype.readOnlyChanged = function (val) {
- this.div.contentEditable = String(val != "nocursor")
-};
-
-ContentEditableInput.prototype.onContextMenu = function () {};
-ContentEditableInput.prototype.resetPosition = function () {};
-
-ContentEditableInput.prototype.needsContentAttribute = true
-
-function posToDOM(cm, pos) {
- var view = findViewForLine(cm, pos.line)
- if (!view || view.hidden) { return null }
- var line = getLine(cm.doc, pos.line)
- var info = mapFromLineView(view, line, pos.line)
-
- var order = getOrder(line, cm.doc.direction), side = "left"
- if (order) {
- var partPos = getBidiPartAt(order, pos.ch)
- side = partPos % 2 ? "right" : "left"
- }
- var result = nodeAndOffsetInLineMap(info.map, pos.ch, side)
- result.offset = result.collapse == "right" ? result.end : result.start
- return result
-}
-
-function isInGutter(node) {
- for (var scan = node; scan; scan = scan.parentNode)
- { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }
- return false
-}
-
-function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }
-
-function domTextBetween(cm, from, to, fromLine, toLine) {
- var text = "", closing = false, lineSep = cm.doc.lineSeparator()
- function recognizeMarker(id) { return function (marker) { return marker.id == id; } }
- function close() {
- if (closing) {
- text += lineSep
- closing = false
- }
- }
- function addText(str) {
- if (str) {
- close()
- text += str
- }
- }
- function walk(node) {
- if (node.nodeType == 1) {
- var cmText = node.getAttribute("cm-text")
- if (cmText != null) {
- addText(cmText || node.textContent.replace(/\u200b/g, ""))
- return
- }
- var markerID = node.getAttribute("cm-marker"), range
- if (markerID) {
- var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID))
- if (found.length && (range = found[0].find()))
- { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) }
- return
- }
- if (node.getAttribute("contenteditable") == "false") { return }
- var isBlock = /^(pre|div|p)$/i.test(node.nodeName)
- if (isBlock) { close() }
- for (var i = 0; i < node.childNodes.length; i++)
- { walk(node.childNodes[i]) }
- if (isBlock) { closing = true }
- } else if (node.nodeType == 3) {
- addText(node.nodeValue)
- }
- }
- for (;;) {
- walk(from)
- if (from == to) { break }
- from = from.nextSibling
- }
- return text
-}
-
-function domToPos(cm, node, offset) {
- var lineNode
- if (node == cm.display.lineDiv) {
- lineNode = cm.display.lineDiv.childNodes[offset]
- if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }
- node = null; offset = 0
- } else {
- for (lineNode = node;; lineNode = lineNode.parentNode) {
- if (!lineNode || lineNode == cm.display.lineDiv) { return null }
- if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }
- }
- }
- for (var i = 0; i < cm.display.view.length; i++) {
- var lineView = cm.display.view[i]
- if (lineView.node == lineNode)
- { return locateNodeInLineView(lineView, node, offset) }
- }
-}
-
-function locateNodeInLineView(lineView, node, offset) {
- var wrapper = lineView.text.firstChild, bad = false
- if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }
- if (node == wrapper) {
- bad = true
- node = wrapper.childNodes[offset]
- offset = 0
- if (!node) {
- var line = lineView.rest ? lst(lineView.rest) : lineView.line
- return badPos(Pos(lineNo(line), line.text.length), bad)
- }
- }
-
- var textNode = node.nodeType == 3 ? node : null, topNode = node
- if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
- textNode = node.firstChild
- if (offset) { offset = textNode.nodeValue.length }
- }
- while (topNode.parentNode != wrapper) { topNode = topNode.parentNode }
- var measure = lineView.measure, maps = measure.maps
-
- function find(textNode, topNode, offset) {
- for (var i = -1; i < (maps ? maps.length : 0); i++) {
- var map = i < 0 ? measure.map : maps[i]
- for (var j = 0; j < map.length; j += 3) {
- var curNode = map[j + 2]
- if (curNode == textNode || curNode == topNode) {
- var line = lineNo(i < 0 ? lineView.line : lineView.rest[i])
- var ch = map[j] + offset
- if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] }
- return Pos(line, ch)
- }
- }
- }
- }
- var found = find(textNode, topNode, offset)
- if (found) { return badPos(found, bad) }
-
- // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
- for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
- found = find(after, after.firstChild, 0)
- if (found)
- { return badPos(Pos(found.line, found.ch - dist), bad) }
- else
- { dist += after.textContent.length }
- }
- for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {
- found = find(before, before.firstChild, -1)
- if (found)
- { return badPos(Pos(found.line, found.ch + dist$1), bad) }
- else
- { dist$1 += before.textContent.length }
- }
-}
-
-// TEXTAREA INPUT STYLE
-
-var TextareaInput = function(cm) {
- this.cm = cm
- // See input.poll and input.reset
- this.prevInput = ""
-
- // Flag that indicates whether we expect input to appear real soon
- // now (after some event like 'keypress' or 'input') and are
- // polling intensively.
- this.pollingFast = false
- // Self-resetting timeout for the poller
- this.polling = new Delayed()
- // Used to work around IE issue with selection being forgotten when focus moves away from textarea
- this.hasSelection = false
- this.composing = null
-};
-
-TextareaInput.prototype.init = function (display) {
- var this$1 = this;
-
- var input = this, cm = this.cm
-
- // Wraps and hides input textarea
- var div = this.wrapper = hiddenTextarea()
- // The semihidden textarea that is focused when the editor is
- // focused, and receives input.
- var te = this.textarea = div.firstChild
- display.wrapper.insertBefore(div, display.wrapper.firstChild)
-
- // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
- if (ios) { te.style.width = "0px" }
-
- on(te, "input", function () {
- if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null }
- input.poll()
- })
-
- on(te, "paste", function (e) {
- if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
-
- cm.state.pasteIncoming = true
- input.fastPoll()
- })
-
- function prepareCopyCut(e) {
- if (signalDOMEvent(cm, e)) { return }
- if (cm.somethingSelected()) {
- setLastCopied({lineWise: false, text: cm.getSelections()})
- } else if (!cm.options.lineWiseCopyCut) {
- return
- } else {
- var ranges = copyableRanges(cm)
- setLastCopied({lineWise: true, text: ranges.text})
- if (e.type == "cut") {
- cm.setSelections(ranges.ranges, null, sel_dontScroll)
- } else {
- input.prevInput = ""
- te.value = ranges.text.join("\n")
- selectInput(te)
- }
- }
- if (e.type == "cut") { cm.state.cutIncoming = true }
- }
- on(te, "cut", prepareCopyCut)
- on(te, "copy", prepareCopyCut)
-
- on(display.scroller, "paste", function (e) {
- if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
- cm.state.pasteIncoming = true
- input.focus()
- })
-
- // Prevent normal selection in the editor (we handle our own)
- on(display.lineSpace, "selectstart", function (e) {
- if (!eventInWidget(display, e)) { e_preventDefault(e) }
- })
-
- on(te, "compositionstart", function () {
- var start = cm.getCursor("from")
- if (input.composing) { input.composing.range.clear() }
- input.composing = {
- start: start,
- range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
- }
- })
- on(te, "compositionend", function () {
- if (input.composing) {
- input.poll()
- input.composing.range.clear()
- input.composing = null
- }
- })
-};
-
-TextareaInput.prototype.prepareSelection = function () {
- // Redraw the selection and/or cursor
- var cm = this.cm, display = cm.display, doc = cm.doc
- var result = prepareSelection(cm)
-
- // Move the hidden textarea near the cursor to prevent scrolling artifacts
- if (cm.options.moveInputWithCursor) {
- var headPos = cursorCoords(cm, doc.sel.primary().head, "div")
- var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect()
- result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
- headPos.top + lineOff.top - wrapOff.top))
- result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
- headPos.left + lineOff.left - wrapOff.left))
- }
-
- return result
-};
-
-TextareaInput.prototype.showSelection = function (drawn) {
- var cm = this.cm, display = cm.display
- removeChildrenAndAdd(display.cursorDiv, drawn.cursors)
- removeChildrenAndAdd(display.selectionDiv, drawn.selection)
- if (drawn.teTop != null) {
- this.wrapper.style.top = drawn.teTop + "px"
- this.wrapper.style.left = drawn.teLeft + "px"
- }
-};
-
-// Reset the input to correspond to the selection (or to be empty,
-// when not typing and nothing is selected)
-TextareaInput.prototype.reset = function (typing) {
- if (this.contextMenuPending || this.composing) { return }
- var cm = this.cm
- if (cm.somethingSelected()) {
- this.prevInput = ""
- var content = cm.getSelection()
- this.textarea.value = content
- if (cm.state.focused) { selectInput(this.textarea) }
- if (ie && ie_version >= 9) { this.hasSelection = content }
- } else if (!typing) {
- this.prevInput = this.textarea.value = ""
- if (ie && ie_version >= 9) { this.hasSelection = null }
- }
-};
-
-TextareaInput.prototype.getField = function () { return this.textarea };
-
-TextareaInput.prototype.supportsTouch = function () { return false };
-
-TextareaInput.prototype.focus = function () {
- if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
- try { this.textarea.focus() }
- catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
- }
-};
-
-TextareaInput.prototype.blur = function () { this.textarea.blur() };
-
-TextareaInput.prototype.resetPosition = function () {
- this.wrapper.style.top = this.wrapper.style.left = 0
-};
-
-TextareaInput.prototype.receivedFocus = function () { this.slowPoll() };
-
-// Poll for input changes, using the normal rate of polling. This
-// runs as long as the editor is focused.
-TextareaInput.prototype.slowPoll = function () {
- var this$1 = this;
-
- if (this.pollingFast) { return }
- this.polling.set(this.cm.options.pollInterval, function () {
- this$1.poll()
- if (this$1.cm.state.focused) { this$1.slowPoll() }
- })
-};
-
-// When an event has just come in that is likely to add or change
-// something in the input textarea, we poll faster, to ensure that
-// the change appears on the screen quickly.
-TextareaInput.prototype.fastPoll = function () {
- var missed = false, input = this
- input.pollingFast = true
- function p() {
- var changed = input.poll()
- if (!changed && !missed) {missed = true; input.polling.set(60, p)}
- else {input.pollingFast = false; input.slowPoll()}
- }
- input.polling.set(20, p)
-};
-
-// Read input from the textarea, and update the document to match.
-// When something is selected, it is present in the textarea, and
-// selected (unless it is huge, in which case a placeholder is
-// used). When nothing is selected, the cursor sits after previously
-// seen text (can be empty), which is stored in prevInput (we must
-// not reset the textarea when typing, because that breaks IME).
-TextareaInput.prototype.poll = function () {
- var this$1 = this;
-
- var cm = this.cm, input = this.textarea, prevInput = this.prevInput
- // Since this is called a *lot*, try to bail out as cheaply as
- // possible when it is clear that nothing happened. hasSelection
- // will be the case when there is a lot of text in the textarea,
- // in which case reading its value would be expensive.
- if (this.contextMenuPending || !cm.state.focused ||
- (hasSelection(input) && !prevInput && !this.composing) ||
- cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
- { return false }
-
- var text = input.value
- // If nothing changed, bail.
- if (text == prevInput && !cm.somethingSelected()) { return false }
- // Work around nonsensical selection resetting in IE9/10, and
- // inexplicable appearance of private area unicode characters on
- // some key combos in Mac (#2689).
- if (ie && ie_version >= 9 && this.hasSelection === text ||
- mac && /[\uf700-\uf7ff]/.test(text)) {
- cm.display.input.reset()
- return false
- }
-
- if (cm.doc.sel == cm.display.selForContextMenu) {
- var first = text.charCodeAt(0)
- if (first == 0x200b && !prevInput) { prevInput = "\u200b" }
- if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") }
- }
- // Find the part of the input that is actually new
- var same = 0, l = Math.min(prevInput.length, text.length)
- while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same }
-
- runInOp(cm, function () {
- applyTextInput(cm, text.slice(same), prevInput.length - same,
- null, this$1.composing ? "*compose" : null)
-
- // Don't leave long text in the textarea, since it makes further polling slow
- if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" }
- else { this$1.prevInput = text }
-
- if (this$1.composing) {
- this$1.composing.range.clear()
- this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"),
- {className: "CodeMirror-composing"})
- }
- })
- return true
-};
-
-TextareaInput.prototype.ensurePolled = function () {
- if (this.pollingFast && this.poll()) { this.pollingFast = false }
-};
-
-TextareaInput.prototype.onKeyPress = function () {
- if (ie && ie_version >= 9) { this.hasSelection = null }
- this.fastPoll()
-};
-
-TextareaInput.prototype.onContextMenu = function (e) {
- var input = this, cm = input.cm, display = cm.display, te = input.textarea
- var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop
- if (!pos || presto) { return } // Opera is difficult.
-
- // Reset the current text selection only if the click is done outside of the selection
- // and 'resetSelectionOnContextMenu' option is true.
- var reset = cm.options.resetSelectionOnContextMenu
- if (reset && cm.doc.sel.contains(pos) == -1)
- { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) }
-
- var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
- input.wrapper.style.cssText = "position: absolute"
- var wrapperBox = input.wrapper.getBoundingClientRect()
- te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"
- var oldScrollY
- if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712)
- display.input.focus()
- if (webkit) { window.scrollTo(null, oldScrollY) }
- display.input.reset()
- // Adds "Select all" to context menu in FF
- if (!cm.somethingSelected()) { te.value = input.prevInput = " " }
- input.contextMenuPending = true
- display.selForContextMenu = cm.doc.sel
- clearTimeout(display.detectingSelectAll)
-
- // Select-all will be greyed out if there's nothing to select, so
- // this adds a zero-width space so that we can later check whether
- // it got selected.
- function prepareSelectAllHack() {
- if (te.selectionStart != null) {
- var selected = cm.somethingSelected()
- var extval = "\u200b" + (selected ? te.value : "")
- te.value = "\u21da" // Used to catch context-menu undo
- te.value = extval
- input.prevInput = selected ? "" : "\u200b"
- te.selectionStart = 1; te.selectionEnd = extval.length
- // Re-set this, in case some other handler touched the
- // selection in the meantime.
- display.selForContextMenu = cm.doc.sel
- }
- }
- function rehide() {
- input.contextMenuPending = false
- input.wrapper.style.cssText = oldWrapperCSS
- te.style.cssText = oldCSS
- if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) }
-
- // Try to detect the user choosing select-all
- if (te.selectionStart != null) {
- if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() }
- var i = 0, poll = function () {
- if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
- te.selectionEnd > 0 && input.prevInput == "\u200b") {
- operation(cm, selectAll)(cm)
- } else if (i++ < 10) {
- display.detectingSelectAll = setTimeout(poll, 500)
- } else {
- display.selForContextMenu = null
- display.input.reset()
- }
- }
- display.detectingSelectAll = setTimeout(poll, 200)
- }
- }
-
- if (ie && ie_version >= 9) { prepareSelectAllHack() }
- if (captureRightClick) {
- e_stop(e)
- var mouseup = function () {
- off(window, "mouseup", mouseup)
- setTimeout(rehide, 20)
- }
- on(window, "mouseup", mouseup)
- } else {
- setTimeout(rehide, 50)
- }
-};
-
-TextareaInput.prototype.readOnlyChanged = function (val) {
- if (!val) { this.reset() }
- this.textarea.disabled = val == "nocursor"
-};
-
-TextareaInput.prototype.setUneditable = function () {};
-
-TextareaInput.prototype.needsContentAttribute = false
-
-function fromTextArea(textarea, options) {
- options = options ? copyObj(options) : {}
- options.value = textarea.value
- if (!options.tabindex && textarea.tabIndex)
- { options.tabindex = textarea.tabIndex }
- if (!options.placeholder && textarea.placeholder)
- { options.placeholder = textarea.placeholder }
- // Set autofocus to true if this textarea is focused, or if it has
- // autofocus and no other element is focused.
- if (options.autofocus == null) {
- var hasFocus = activeElt()
- options.autofocus = hasFocus == textarea ||
- textarea.getAttribute("autofocus") != null && hasFocus == document.body
- }
-
- function save() {textarea.value = cm.getValue()}
-
- var realSubmit
- if (textarea.form) {
- on(textarea.form, "submit", save)
- // Deplorable hack to make the submit method do the right thing.
- if (!options.leaveSubmitMethodAlone) {
- var form = textarea.form
- realSubmit = form.submit
- try {
- var wrappedSubmit = form.submit = function () {
- save()
- form.submit = realSubmit
- form.submit()
- form.submit = wrappedSubmit
- }
- } catch(e) {}
- }
- }
-
- options.finishInit = function (cm) {
- cm.save = save
- cm.getTextArea = function () { return textarea; }
- cm.toTextArea = function () {
- cm.toTextArea = isNaN // Prevent this from being ran twice
- save()
- textarea.parentNode.removeChild(cm.getWrapperElement())
- textarea.style.display = ""
- if (textarea.form) {
- off(textarea.form, "submit", save)
- if (typeof textarea.form.submit == "function")
- { textarea.form.submit = realSubmit }
- }
- }
- }
-
- textarea.style.display = "none"
- var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },
- options)
- return cm
-}
-
-function addLegacyProps(CodeMirror) {
- CodeMirror.off = off
- CodeMirror.on = on
- CodeMirror.wheelEventPixels = wheelEventPixels
- CodeMirror.Doc = Doc
- CodeMirror.splitLines = splitLinesAuto
- CodeMirror.countColumn = countColumn
- CodeMirror.findColumn = findColumn
- CodeMirror.isWordChar = isWordCharBasic
- CodeMirror.Pass = Pass
- CodeMirror.signal = signal
- CodeMirror.Line = Line
- CodeMirror.changeEnd = changeEnd
- CodeMirror.scrollbarModel = scrollbarModel
- CodeMirror.Pos = Pos
- CodeMirror.cmpPos = cmp
- CodeMirror.modes = modes
- CodeMirror.mimeModes = mimeModes
- CodeMirror.resolveMode = resolveMode
- CodeMirror.getMode = getMode
- CodeMirror.modeExtensions = modeExtensions
- CodeMirror.extendMode = extendMode
- CodeMirror.copyState = copyState
- CodeMirror.startState = startState
- CodeMirror.innerMode = innerMode
- CodeMirror.commands = commands
- CodeMirror.keyMap = keyMap
- CodeMirror.keyName = keyName
- CodeMirror.isModifierKey = isModifierKey
- CodeMirror.lookupKey = lookupKey
- CodeMirror.normalizeKeyMap = normalizeKeyMap
- CodeMirror.StringStream = StringStream
- CodeMirror.SharedTextMarker = SharedTextMarker
- CodeMirror.TextMarker = TextMarker
- CodeMirror.LineWidget = LineWidget
- CodeMirror.e_preventDefault = e_preventDefault
- CodeMirror.e_stopPropagation = e_stopPropagation
- CodeMirror.e_stop = e_stop
- CodeMirror.addClass = addClass
- CodeMirror.contains = contains
- CodeMirror.rmClass = rmClass
- CodeMirror.keyNames = keyNames
-}
-
-// EDITOR CONSTRUCTOR
-
-defineOptions(CodeMirror)
-
-addEditorMethods(CodeMirror)
-
-// Set up methods on CodeMirror's prototype to redirect to the editor's document.
-var dontDelegate = "iter insert remove copy getEditor constructor".split(" ")
-for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
- { CodeMirror.prototype[prop] = (function(method) {
- return function() {return method.apply(this.doc, arguments)}
- })(Doc.prototype[prop]) } }
-
-eventMixin(Doc)
-
-// INPUT HANDLING
-
-CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}
-
-// MODE DEFINITION AND QUERYING
-
-// Extra arguments are stored as the mode's dependencies, which is
-// used by (legacy) mechanisms like loadmode.js to automatically
-// load a mode. (Preferred mechanism is the require/define calls.)
-CodeMirror.defineMode = function(name/*, mode, …*/) {
- if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name }
- defineMode.apply(this, arguments)
-}
-
-CodeMirror.defineMIME = defineMIME
-
-// Minimal default mode.
-CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); })
-CodeMirror.defineMIME("text/plain", "null")
-
-// EXTENSIONS
-
-CodeMirror.defineExtension = function (name, func) {
- CodeMirror.prototype[name] = func
-}
-CodeMirror.defineDocExtension = function (name, func) {
- Doc.prototype[name] = func
-}
-
-CodeMirror.fromTextArea = fromTextArea
-
-addLegacyProps(CodeMirror)
-
-CodeMirror.version = "5.28.0"
-
-return CodeMirror;
-
-})));
\ No newline at end of file
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/css.js b/js/DevHelper/Lib/CodeMirror/mode/css/css.js
deleted file mode 100644
index 056c48e..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/css.js
+++ /dev/null
@@ -1,830 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("css", function(config, parserConfig) {
- var inline = parserConfig.inline
- if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
-
- var indentUnit = config.indentUnit,
- tokenHooks = parserConfig.tokenHooks,
- documentTypes = parserConfig.documentTypes || {},
- mediaTypes = parserConfig.mediaTypes || {},
- mediaFeatures = parserConfig.mediaFeatures || {},
- mediaValueKeywords = parserConfig.mediaValueKeywords || {},
- propertyKeywords = parserConfig.propertyKeywords || {},
- nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
- fontProperties = parserConfig.fontProperties || {},
- counterDescriptors = parserConfig.counterDescriptors || {},
- colorKeywords = parserConfig.colorKeywords || {},
- valueKeywords = parserConfig.valueKeywords || {},
- allowNested = parserConfig.allowNested,
- lineComment = parserConfig.lineComment,
- supportsAtComponent = parserConfig.supportsAtComponent === true;
-
- var type, override;
- function ret(style, tp) { type = tp; return style; }
-
- // Tokenizers
-
- function tokenBase(stream, state) {
- var ch = stream.next();
- if (tokenHooks[ch]) {
- var result = tokenHooks[ch](stream, state);
- if (result !== false) return result;
- }
- if (ch == "@") {
- stream.eatWhile(/[\w\\\-]/);
- return ret("def", stream.current());
- } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
- return ret(null, "compare");
- } else if (ch == "\"" || ch == "'") {
- state.tokenize = tokenString(ch);
- return state.tokenize(stream, state);
- } else if (ch == "#") {
- stream.eatWhile(/[\w\\\-]/);
- return ret("atom", "hash");
- } else if (ch == "!") {
- stream.match(/^\s*\w*/);
- return ret("keyword", "important");
- } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
- stream.eatWhile(/[\w.%]/);
- return ret("number", "unit");
- } else if (ch === "-") {
- if (/[\d.]/.test(stream.peek())) {
- stream.eatWhile(/[\w.%]/);
- return ret("number", "unit");
- } else if (stream.match(/^-[\w\\\-]+/)) {
- stream.eatWhile(/[\w\\\-]/);
- if (stream.match(/^\s*:/, false))
- return ret("variable-2", "variable-definition");
- return ret("variable-2", "variable");
- } else if (stream.match(/^\w+-/)) {
- return ret("meta", "meta");
- }
- } else if (/[,+>*\/]/.test(ch)) {
- return ret(null, "select-op");
- } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
- return ret("qualifier", "qualifier");
- } else if (/[:;{}\[\]\(\)]/.test(ch)) {
- return ret(null, ch);
- } else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) ||
- (ch == "d" && stream.match("omain(")) ||
- (ch == "r" && stream.match("egexp("))) {
- stream.backUp(1);
- state.tokenize = tokenParenthesized;
- return ret("property", "word");
- } else if (/[\w\\\-]/.test(ch)) {
- stream.eatWhile(/[\w\\\-]/);
- return ret("property", "word");
- } else {
- return ret(null, null);
- }
- }
-
- function tokenString(quote) {
- return function(stream, state) {
- var escaped = false, ch;
- while ((ch = stream.next()) != null) {
- if (ch == quote && !escaped) {
- if (quote == ")") stream.backUp(1);
- break;
- }
- escaped = !escaped && ch == "\\";
- }
- if (ch == quote || !escaped && quote != ")") state.tokenize = null;
- return ret("string", "string");
- };
- }
-
- function tokenParenthesized(stream, state) {
- stream.next(); // Must be '('
- if (!stream.match(/\s*[\"\')]/, false))
- state.tokenize = tokenString(")");
- else
- state.tokenize = null;
- return ret(null, "(");
- }
-
- // Context management
-
- function Context(type, indent, prev) {
- this.type = type;
- this.indent = indent;
- this.prev = prev;
- }
-
- function pushContext(state, stream, type, indent) {
- state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
- return type;
- }
-
- function popContext(state) {
- if (state.context.prev)
- state.context = state.context.prev;
- return state.context.type;
- }
-
- function pass(type, stream, state) {
- return states[state.context.type](type, stream, state);
- }
- function popAndPass(type, stream, state, n) {
- for (var i = n || 1; i > 0; i--)
- state.context = state.context.prev;
- return pass(type, stream, state);
- }
-
- // Parser
-
- function wordAsValue(stream) {
- var word = stream.current().toLowerCase();
- if (valueKeywords.hasOwnProperty(word))
- override = "atom";
- else if (colorKeywords.hasOwnProperty(word))
- override = "keyword";
- else
- override = "variable";
- }
-
- var states = {};
-
- states.top = function(type, stream, state) {
- if (type == "{") {
- return pushContext(state, stream, "block");
- } else if (type == "}" && state.context.prev) {
- return popContext(state);
- } else if (supportsAtComponent && /@component/.test(type)) {
- return pushContext(state, stream, "atComponentBlock");
- } else if (/^@(-moz-)?document$/.test(type)) {
- return pushContext(state, stream, "documentTypes");
- } else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
- return pushContext(state, stream, "atBlock");
- } else if (/^@(font-face|counter-style)/.test(type)) {
- state.stateArg = type;
- return "restricted_atBlock_before";
- } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
- return "keyframes";
- } else if (type && type.charAt(0) == "@") {
- return pushContext(state, stream, "at");
- } else if (type == "hash") {
- override = "builtin";
- } else if (type == "word") {
- override = "tag";
- } else if (type == "variable-definition") {
- return "maybeprop";
- } else if (type == "interpolation") {
- return pushContext(state, stream, "interpolation");
- } else if (type == ":") {
- return "pseudo";
- } else if (allowNested && type == "(") {
- return pushContext(state, stream, "parens");
- }
- return state.context.type;
- };
-
- states.block = function(type, stream, state) {
- if (type == "word") {
- var word = stream.current().toLowerCase();
- if (propertyKeywords.hasOwnProperty(word)) {
- override = "property";
- return "maybeprop";
- } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
- override = "string-2";
- return "maybeprop";
- } else if (allowNested) {
- override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
- return "block";
- } else {
- override += " error";
- return "maybeprop";
- }
- } else if (type == "meta") {
- return "block";
- } else if (!allowNested && (type == "hash" || type == "qualifier")) {
- override = "error";
- return "block";
- } else {
- return states.top(type, stream, state);
- }
- };
-
- states.maybeprop = function(type, stream, state) {
- if (type == ":") return pushContext(state, stream, "prop");
- return pass(type, stream, state);
- };
-
- states.prop = function(type, stream, state) {
- if (type == ";") return popContext(state);
- if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
- if (type == "}" || type == "{") return popAndPass(type, stream, state);
- if (type == "(") return pushContext(state, stream, "parens");
-
- if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
- override += " error";
- } else if (type == "word") {
- wordAsValue(stream);
- } else if (type == "interpolation") {
- return pushContext(state, stream, "interpolation");
- }
- return "prop";
- };
-
- states.propBlock = function(type, _stream, state) {
- if (type == "}") return popContext(state);
- if (type == "word") { override = "property"; return "maybeprop"; }
- return state.context.type;
- };
-
- states.parens = function(type, stream, state) {
- if (type == "{" || type == "}") return popAndPass(type, stream, state);
- if (type == ")") return popContext(state);
- if (type == "(") return pushContext(state, stream, "parens");
- if (type == "interpolation") return pushContext(state, stream, "interpolation");
- if (type == "word") wordAsValue(stream);
- return "parens";
- };
-
- states.pseudo = function(type, stream, state) {
- if (type == "meta") return "pseudo";
-
- if (type == "word") {
- override = "variable-3";
- return state.context.type;
- }
- return pass(type, stream, state);
- };
-
- states.documentTypes = function(type, stream, state) {
- if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
- override = "tag";
- return state.context.type;
- } else {
- return states.atBlock(type, stream, state);
- }
- };
-
- states.atBlock = function(type, stream, state) {
- if (type == "(") return pushContext(state, stream, "atBlock_parens");
- if (type == "}" || type == ";") return popAndPass(type, stream, state);
- if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
-
- if (type == "interpolation") return pushContext(state, stream, "interpolation");
-
- if (type == "word") {
- var word = stream.current().toLowerCase();
- if (word == "only" || word == "not" || word == "and" || word == "or")
- override = "keyword";
- else if (mediaTypes.hasOwnProperty(word))
- override = "attribute";
- else if (mediaFeatures.hasOwnProperty(word))
- override = "property";
- else if (mediaValueKeywords.hasOwnProperty(word))
- override = "keyword";
- else if (propertyKeywords.hasOwnProperty(word))
- override = "property";
- else if (nonStandardPropertyKeywords.hasOwnProperty(word))
- override = "string-2";
- else if (valueKeywords.hasOwnProperty(word))
- override = "atom";
- else if (colorKeywords.hasOwnProperty(word))
- override = "keyword";
- else
- override = "error";
- }
- return state.context.type;
- };
-
- states.atComponentBlock = function(type, stream, state) {
- if (type == "}")
- return popAndPass(type, stream, state);
- if (type == "{")
- return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
- if (type == "word")
- override = "error";
- return state.context.type;
- };
-
- states.atBlock_parens = function(type, stream, state) {
- if (type == ")") return popContext(state);
- if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
- return states.atBlock(type, stream, state);
- };
-
- states.restricted_atBlock_before = function(type, stream, state) {
- if (type == "{")
- return pushContext(state, stream, "restricted_atBlock");
- if (type == "word" && state.stateArg == "@counter-style") {
- override = "variable";
- return "restricted_atBlock_before";
- }
- return pass(type, stream, state);
- };
-
- states.restricted_atBlock = function(type, stream, state) {
- if (type == "}") {
- state.stateArg = null;
- return popContext(state);
- }
- if (type == "word") {
- if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
- (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
- override = "error";
- else
- override = "property";
- return "maybeprop";
- }
- return "restricted_atBlock";
- };
-
- states.keyframes = function(type, stream, state) {
- if (type == "word") { override = "variable"; return "keyframes"; }
- if (type == "{") return pushContext(state, stream, "top");
- return pass(type, stream, state);
- };
-
- states.at = function(type, stream, state) {
- if (type == ";") return popContext(state);
- if (type == "{" || type == "}") return popAndPass(type, stream, state);
- if (type == "word") override = "tag";
- else if (type == "hash") override = "builtin";
- return "at";
- };
-
- states.interpolation = function(type, stream, state) {
- if (type == "}") return popContext(state);
- if (type == "{" || type == ";") return popAndPass(type, stream, state);
- if (type == "word") override = "variable";
- else if (type != "variable" && type != "(" && type != ")") override = "error";
- return "interpolation";
- };
-
- return {
- startState: function(base) {
- return {tokenize: null,
- state: inline ? "block" : "top",
- stateArg: null,
- context: new Context(inline ? "block" : "top", base || 0, null)};
- },
-
- token: function(stream, state) {
- if (!state.tokenize && stream.eatSpace()) return null;
- var style = (state.tokenize || tokenBase)(stream, state);
- if (style && typeof style == "object") {
- type = style[1];
- style = style[0];
- }
- override = style;
- state.state = states[state.state](type, stream, state);
- return override;
- },
-
- indent: function(state, textAfter) {
- var cx = state.context, ch = textAfter && textAfter.charAt(0);
- var indent = cx.indent;
- if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
- if (cx.prev) {
- if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
- cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
- // Resume indentation from parent context.
- cx = cx.prev;
- indent = cx.indent;
- } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
- ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
- // Dedent relative to current context.
- indent = Math.max(0, cx.indent - indentUnit);
- }
- }
- return indent;
- },
-
- electricChars: "}",
- blockCommentStart: "/*",
- blockCommentEnd: "*/",
- lineComment: lineComment,
- fold: "brace"
- };
-});
-
- function keySet(array) {
- var keys = {};
- for (var i = 0; i < array.length; ++i) {
- keys[array[i].toLowerCase()] = true;
- }
- return keys;
- }
-
- var documentTypes_ = [
- "domain", "regexp", "url", "url-prefix"
- ], documentTypes = keySet(documentTypes_);
-
- var mediaTypes_ = [
- "all", "aural", "braille", "handheld", "print", "projection", "screen",
- "tty", "tv", "embossed"
- ], mediaTypes = keySet(mediaTypes_);
-
- var mediaFeatures_ = [
- "width", "min-width", "max-width", "height", "min-height", "max-height",
- "device-width", "min-device-width", "max-device-width", "device-height",
- "min-device-height", "max-device-height", "aspect-ratio",
- "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
- "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
- "max-color", "color-index", "min-color-index", "max-color-index",
- "monochrome", "min-monochrome", "max-monochrome", "resolution",
- "min-resolution", "max-resolution", "scan", "grid", "orientation",
- "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
- "pointer", "any-pointer", "hover", "any-hover"
- ], mediaFeatures = keySet(mediaFeatures_);
-
- var mediaValueKeywords_ = [
- "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
- "interlace", "progressive"
- ], mediaValueKeywords = keySet(mediaValueKeywords_);
-
- var propertyKeywords_ = [
- "align-content", "align-items", "align-self", "alignment-adjust",
- "alignment-baseline", "anchor-point", "animation", "animation-delay",
- "animation-direction", "animation-duration", "animation-fill-mode",
- "animation-iteration-count", "animation-name", "animation-play-state",
- "animation-timing-function", "appearance", "azimuth", "backface-visibility",
- "background", "background-attachment", "background-blend-mode", "background-clip",
- "background-color", "background-image", "background-origin", "background-position",
- "background-repeat", "background-size", "baseline-shift", "binding",
- "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
- "bookmark-target", "border", "border-bottom", "border-bottom-color",
- "border-bottom-left-radius", "border-bottom-right-radius",
- "border-bottom-style", "border-bottom-width", "border-collapse",
- "border-color", "border-image", "border-image-outset",
- "border-image-repeat", "border-image-slice", "border-image-source",
- "border-image-width", "border-left", "border-left-color",
- "border-left-style", "border-left-width", "border-radius", "border-right",
- "border-right-color", "border-right-style", "border-right-width",
- "border-spacing", "border-style", "border-top", "border-top-color",
- "border-top-left-radius", "border-top-right-radius", "border-top-style",
- "border-top-width", "border-width", "bottom", "box-decoration-break",
- "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
- "caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count",
- "column-fill", "column-gap", "column-rule", "column-rule-color",
- "column-rule-style", "column-rule-width", "column-span", "column-width",
- "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
- "cue-after", "cue-before", "cursor", "direction", "display",
- "dominant-baseline", "drop-initial-after-adjust",
- "drop-initial-after-align", "drop-initial-before-adjust",
- "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
- "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
- "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
- "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
- "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
- "font-stretch", "font-style", "font-synthesis", "font-variant",
- "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
- "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
- "font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
- "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap",
- "grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap",
- "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns",
- "grid-template-rows", "hanging-punctuation", "height", "hyphens",
- "icon", "image-orientation", "image-rendering", "image-resolution",
- "inline-box-align", "justify-content", "justify-items", "justify-self", "left", "letter-spacing",
- "line-break", "line-height", "line-stacking", "line-stacking-ruby",
- "line-stacking-shift", "line-stacking-strategy", "list-style",
- "list-style-image", "list-style-position", "list-style-type", "margin",
- "margin-bottom", "margin-left", "margin-right", "margin-top",
- "marks", "marquee-direction", "marquee-loop",
- "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
- "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
- "nav-left", "nav-right", "nav-up", "object-fit", "object-position",
- "opacity", "order", "orphans", "outline",
- "outline-color", "outline-offset", "outline-style", "outline-width",
- "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
- "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
- "page", "page-break-after", "page-break-before", "page-break-inside",
- "page-policy", "pause", "pause-after", "pause-before", "perspective",
- "perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position",
- "presentation-level", "punctuation-trim", "quotes", "region-break-after",
- "region-break-before", "region-break-inside", "region-fragment",
- "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
- "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
- "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
- "shape-outside", "size", "speak", "speak-as", "speak-header",
- "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
- "tab-size", "table-layout", "target", "target-name", "target-new",
- "target-position", "text-align", "text-align-last", "text-decoration",
- "text-decoration-color", "text-decoration-line", "text-decoration-skip",
- "text-decoration-style", "text-emphasis", "text-emphasis-color",
- "text-emphasis-position", "text-emphasis-style", "text-height",
- "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
- "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
- "text-wrap", "top", "transform", "transform-origin", "transform-style",
- "transition", "transition-delay", "transition-duration",
- "transition-property", "transition-timing-function", "unicode-bidi",
- "user-select", "vertical-align", "visibility", "voice-balance", "voice-duration",
- "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
- "voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break",
- "word-spacing", "word-wrap", "z-index",
- // SVG-specific
- "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
- "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
- "color-interpolation", "color-interpolation-filters",
- "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
- "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
- "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
- "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
- "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
- "glyph-orientation-vertical", "text-anchor", "writing-mode"
- ], propertyKeywords = keySet(propertyKeywords_);
-
- var nonStandardPropertyKeywords_ = [
- "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
- "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
- "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
- "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
- "searchfield-results-decoration", "zoom"
- ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
-
- var fontProperties_ = [
- "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
- "font-stretch", "font-weight", "font-style"
- ], fontProperties = keySet(fontProperties_);
-
- var counterDescriptors_ = [
- "additive-symbols", "fallback", "negative", "pad", "prefix", "range",
- "speak-as", "suffix", "symbols", "system"
- ], counterDescriptors = keySet(counterDescriptors_);
-
- var colorKeywords_ = [
- "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
- "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
- "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
- "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
- "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
- "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
- "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
- "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
- "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
- "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
- "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
- "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
- "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
- "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
- "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
- "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
- "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
- "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
- "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
- "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
- "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
- "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
- "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
- "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
- "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
- "whitesmoke", "yellow", "yellowgreen"
- ], colorKeywords = keySet(colorKeywords_);
-
- var valueKeywords_ = [
- "above", "absolute", "activeborder", "additive", "activecaption", "afar",
- "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
- "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
- "arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
- "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
- "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
- "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
- "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
- "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
- "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
- "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
- "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
- "compact", "condensed", "contain", "content", "contents",
- "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
- "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
- "decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
- "destination-in", "destination-out", "destination-over", "devanagari", "difference",
- "disc", "discard", "disclosure-closed", "disclosure-open", "document",
- "dot-dash", "dot-dot-dash",
- "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
- "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
- "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
- "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
- "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
- "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
- "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
- "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
- "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
- "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
- "forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
- "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
- "help", "hidden", "hide", "higher", "highlight", "highlighttext",
- "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
- "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
- "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
- "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
- "italic", "japanese-formal", "japanese-informal", "justify", "kannada",
- "katakana", "katakana-iroha", "keep-all", "khmer",
- "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
- "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
- "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
- "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
- "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
- "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
- "media-controls-background", "media-current-time-display",
- "media-fullscreen-button", "media-mute-button", "media-play-button",
- "media-return-to-realtime-button", "media-rewind-button",
- "media-seek-back-button", "media-seek-forward-button", "media-slider",
- "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
- "media-volume-slider-container", "media-volume-sliderthumb", "medium",
- "menu", "menulist", "menulist-button", "menulist-text",
- "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
- "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
- "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
- "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
- "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
- "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
- "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
- "painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
- "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
- "progress", "push-button", "radial-gradient", "radio", "read-only",
- "read-write", "read-write-plaintext-only", "rectangle", "region",
- "relative", "repeat", "repeating-linear-gradient",
- "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
- "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
- "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
- "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
- "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
- "searchfield-cancel-button", "searchfield-decoration",
- "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
- "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
- "simp-chinese-formal", "simp-chinese-informal", "single",
- "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
- "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
- "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
- "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
- "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
- "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
- "table-caption", "table-cell", "table-column", "table-column-group",
- "table-footer-group", "table-header-group", "table-row", "table-row-group",
- "tamil",
- "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
- "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
- "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
- "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
- "trad-chinese-formal", "trad-chinese-informal", "transform",
- "translate", "translate3d", "translateX", "translateY", "translateZ",
- "transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
- "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
- "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
- "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
- "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
- "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
- "xx-large", "xx-small"
- ], valueKeywords = keySet(valueKeywords_);
-
- var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
- .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
- .concat(valueKeywords_);
- CodeMirror.registerHelper("hintWords", "css", allWords);
-
- function tokenCComment(stream, state) {
- var maybeEnd = false, ch;
- while ((ch = stream.next()) != null) {
- if (maybeEnd && ch == "/") {
- state.tokenize = null;
- break;
- }
- maybeEnd = (ch == "*");
- }
- return ["comment", "comment"];
- }
-
- CodeMirror.defineMIME("text/css", {
- documentTypes: documentTypes,
- mediaTypes: mediaTypes,
- mediaFeatures: mediaFeatures,
- mediaValueKeywords: mediaValueKeywords,
- propertyKeywords: propertyKeywords,
- nonStandardPropertyKeywords: nonStandardPropertyKeywords,
- fontProperties: fontProperties,
- counterDescriptors: counterDescriptors,
- colorKeywords: colorKeywords,
- valueKeywords: valueKeywords,
- tokenHooks: {
- "/": function(stream, state) {
- if (!stream.eat("*")) return false;
- state.tokenize = tokenCComment;
- return tokenCComment(stream, state);
- }
- },
- name: "css"
- });
-
- CodeMirror.defineMIME("text/x-scss", {
- mediaTypes: mediaTypes,
- mediaFeatures: mediaFeatures,
- mediaValueKeywords: mediaValueKeywords,
- propertyKeywords: propertyKeywords,
- nonStandardPropertyKeywords: nonStandardPropertyKeywords,
- colorKeywords: colorKeywords,
- valueKeywords: valueKeywords,
- fontProperties: fontProperties,
- allowNested: true,
- lineComment: "//",
- tokenHooks: {
- "/": function(stream, state) {
- if (stream.eat("/")) {
- stream.skipToEnd();
- return ["comment", "comment"];
- } else if (stream.eat("*")) {
- state.tokenize = tokenCComment;
- return tokenCComment(stream, state);
- } else {
- return ["operator", "operator"];
- }
- },
- ":": function(stream) {
- if (stream.match(/\s*\{/, false))
- return [null, null]
- return false;
- },
- "$": function(stream) {
- stream.match(/^[\w-]+/);
- if (stream.match(/^\s*:/, false))
- return ["variable-2", "variable-definition"];
- return ["variable-2", "variable"];
- },
- "#": function(stream) {
- if (!stream.eat("{")) return false;
- return [null, "interpolation"];
- }
- },
- name: "css",
- helperType: "scss"
- });
-
- CodeMirror.defineMIME("text/x-less", {
- mediaTypes: mediaTypes,
- mediaFeatures: mediaFeatures,
- mediaValueKeywords: mediaValueKeywords,
- propertyKeywords: propertyKeywords,
- nonStandardPropertyKeywords: nonStandardPropertyKeywords,
- colorKeywords: colorKeywords,
- valueKeywords: valueKeywords,
- fontProperties: fontProperties,
- allowNested: true,
- lineComment: "//",
- tokenHooks: {
- "/": function(stream, state) {
- if (stream.eat("/")) {
- stream.skipToEnd();
- return ["comment", "comment"];
- } else if (stream.eat("*")) {
- state.tokenize = tokenCComment;
- return tokenCComment(stream, state);
- } else {
- return ["operator", "operator"];
- }
- },
- "@": function(stream) {
- if (stream.eat("{")) return [null, "interpolation"];
- if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
- stream.eatWhile(/[\w\\\-]/);
- if (stream.match(/^\s*:/, false))
- return ["variable-2", "variable-definition"];
- return ["variable-2", "variable"];
- },
- "&": function() {
- return ["atom", "atom"];
- }
- },
- name: "css",
- helperType: "less"
- });
-
- CodeMirror.defineMIME("text/x-gss", {
- documentTypes: documentTypes,
- mediaTypes: mediaTypes,
- mediaFeatures: mediaFeatures,
- propertyKeywords: propertyKeywords,
- nonStandardPropertyKeywords: nonStandardPropertyKeywords,
- fontProperties: fontProperties,
- counterDescriptors: counterDescriptors,
- colorKeywords: colorKeywords,
- valueKeywords: valueKeywords,
- supportsAtComponent: true,
- tokenHooks: {
- "/": function(stream, state) {
- if (!stream.eat("*")) return false;
- state.tokenize = tokenCComment;
- return tokenCComment(stream, state);
- }
- },
- name: "css",
- helperType: "gss"
- });
-
-});
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/gss.html b/js/DevHelper/Lib/CodeMirror/mode/css/gss.html
deleted file mode 100644
index 232fe8c..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/gss.html
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-CodeMirror: Closure Stylesheets (GSS) mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-Closure Stylesheets (GSS) mode
-
-
-
- A mode for Closure Stylesheets (GSS).
- MIME type defined: text/x-gss.
-
- Parsing/Highlighting Tests: normal , verbose .
-
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/gss_test.js b/js/DevHelper/Lib/CodeMirror/mode/css/gss_test.js
deleted file mode 100644
index d56e592..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/gss_test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- "use strict";
-
- var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss");
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); }
-
- MT("atComponent",
- "[def @component] {",
- "[tag foo] {",
- " [property color]: [keyword black];",
- "}",
- "}");
-
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/index.html b/js/DevHelper/Lib/CodeMirror/mode/css/index.html
deleted file mode 100644
index 0d85311..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/index.html
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-CodeMirror: CSS mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-CSS mode
-
-/* Some example CSS */
-
-@import url("something.css");
-
-body {
- margin: 0;
- padding: 3em 6em;
- font-family: tahoma, arial, sans-serif;
- color: #000;
-}
-
-#navigation a {
- font-weight: bold;
- text-decoration: none !important;
-}
-
-h1 {
- font-size: 2.5em;
-}
-
-h2 {
- font-size: 1.7em;
-}
-
-h1:before, h2:before {
- content: "::";
-}
-
-code {
- font-family: courier, monospace;
- font-size: 80%;
- color: #418A8A;
-}
-
-
-
- MIME types defined: text/css, text/x-scss (demo ), text/x-less (demo ).
-
- Parsing/Highlighting Tests: normal , verbose .
-
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/less.html b/js/DevHelper/Lib/CodeMirror/mode/css/less.html
deleted file mode 100644
index adf7427..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/less.html
+++ /dev/null
@@ -1,152 +0,0 @@
-
-
-CodeMirror: LESS mode
-
-
-
-
-
-
-
-
-
-
-
-LESS mode
-@media screen and (device-aspect-ratio: 16/9) { … }
-@media screen and (device-aspect-ratio: 1280/720) { … }
-@media screen and (device-aspect-ratio: 2560/1440) { … }
-
-html:lang(fr-be)
-
-tr:nth-child(2n+1) /* represents every odd row of an HTML table */
-
-img:nth-of-type(2n+1) { float: right; }
-img:nth-of-type(2n) { float: left; }
-
-body > h2:not(:first-of-type):not(:last-of-type)
-
-html|*:not(:link):not(:visited)
-*|*:not(:hover)
-p::first-line { text-transform: uppercase }
-
-@namespace foo url(http://www.example.com);
-foo|h1 { color: blue } /* first rule */
-
-span[hello="Ocean"][goodbye="Land"]
-
-E[foo]{
- padding:65px;
-}
-
-input[type="search"]::-webkit-search-decoration,
-input[type="search"]::-webkit-search-cancel-button {
- -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
-}
-button::-moz-focus-inner,
-input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
- padding: 0;
- border: 0;
-}
-.btn {
- // reset here as of 2.0.3 due to Recess property order
- border-color: #ccc;
- border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);
-}
-fieldset span button, fieldset span input[type="file"] {
- font-size:12px;
- font-family:Arial, Helvetica, sans-serif;
-}
-
-.rounded-corners (@radius: 5px) {
- border-radius: @radius;
- -webkit-border-radius: @radius;
- -moz-border-radius: @radius;
-}
-
-@import url("something.css");
-
-@light-blue: hsl(190, 50%, 65%);
-
-#menu {
- position: absolute;
- width: 100%;
- z-index: 3;
- clear: both;
- display: block;
- background-color: @blue;
- height: 42px;
- border-top: 2px solid lighten(@alpha-blue, 20%);
- border-bottom: 2px solid darken(@alpha-blue, 25%);
- .box-shadow(0, 1px, 8px, 0.6);
- -moz-box-shadow: 0 0 0 #000; // Because firefox sucks.
-
- &.docked {
- background-color: hsla(210, 60%, 40%, 0.4);
- }
- &:hover {
- background-color: @blue;
- }
-
- #dropdown {
- margin: 0 0 0 117px;
- padding: 0;
- padding-top: 5px;
- display: none;
- width: 190px;
- border-top: 2px solid @medium;
- color: @highlight;
- border: 2px solid darken(@medium, 25%);
- border-left-color: darken(@medium, 15%);
- border-right-color: darken(@medium, 15%);
- border-top-width: 0;
- background-color: darken(@medium, 10%);
- ul {
- padding: 0px;
- }
- li {
- font-size: 14px;
- display: block;
- text-align: left;
- padding: 0;
- border: 0;
- a {
- display: block;
- padding: 0px 15px;
- text-decoration: none;
- color: white;
- &:hover {
- background-color: darken(@medium, 15%);
- text-decoration: none;
- }
- }
- }
- .border-radius(5px, bottom);
- .box-shadow(0, 6px, 8px, 0.5);
- }
-}
-
-
-
- The LESS mode is a sub-mode of the CSS mode (defined in css.js).
-
- Parsing/Highlighting Tests: normal , verbose .
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/less_test.js b/js/DevHelper/Lib/CodeMirror/mode/css/less_test.js
deleted file mode 100644
index dd82155..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/less_test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- "use strict";
-
- var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less");
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); }
-
- MT("variable",
- "[variable-2 @base]: [atom #f04615];",
- "[qualifier .class] {",
- " [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]",
- " [property color]: [variable saturate]([variable-2 @base], [number 5%]);",
- "}");
-
- MT("amp",
- "[qualifier .child], [qualifier .sibling] {",
- " [qualifier .parent] [atom &] {",
- " [property color]: [keyword black];",
- " }",
- " [atom &] + [atom &] {",
- " [property color]: [keyword red];",
- " }",
- "}");
-
- MT("mixin",
- "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {",
- " [property color]: [atom darken]([variable-2 @color], [number 10%]);",
- "}",
- "[qualifier .mixin] ([variable light]; [variable-2 @color]) {",
- " [property color]: [atom lighten]([variable-2 @color], [number 10%]);",
- "}",
- "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {",
- " [property display]: [atom block];",
- "}",
- "[variable-2 @switch]: [variable light];",
- "[qualifier .class] {",
- " [qualifier .mixin]([variable-2 @switch]; [atom #888]);",
- "}");
-
- MT("nest",
- "[qualifier .one] {",
- " [def @media] ([property width]: [number 400px]) {",
- " [property font-size]: [number 1.2em];",
- " [def @media] [attribute print] [keyword and] [property color] {",
- " [property color]: [keyword blue];",
- " }",
- " }",
- "}");
-
-
- MT("interpolation", ".@{[variable foo]} { [property font-weight]: [atom bold]; }");
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/scss.html b/js/DevHelper/Lib/CodeMirror/mode/css/scss.html
deleted file mode 100644
index f8e4d37..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/scss.html
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-CodeMirror: SCSS mode
-
-
-
-
-
-
-
-
-
-
-SCSS mode
-
-/* Some example SCSS */
-
-@import "compass/css3";
-$variable: #333;
-
-$blue: #3bbfce;
-$margin: 16px;
-
-.content-navigation {
- #nested {
- background-color: black;
- }
- border-color: $blue;
- color:
- darken($blue, 9%);
-}
-
-.border {
- padding: $margin / 2;
- margin: $margin / 2;
- border-color: $blue;
-}
-
-@mixin table-base {
- th {
- text-align: center;
- font-weight: bold;
- }
- td, th {padding: 2px}
-}
-
-table.hl {
- margin: 2em 0;
- td.ln {
- text-align: right;
- }
-}
-
-li {
- font: {
- family: serif;
- weight: bold;
- size: 1.2em;
- }
-}
-
-@mixin left($dist) {
- float: left;
- margin-left: $dist;
-}
-
-#data {
- @include left(10px);
- @include table-base;
-}
-
-.source {
- @include flow-into(target);
- border: 10px solid green;
- margin: 20px;
- width: 200px; }
-
-.new-container {
- @include flow-from(target);
- border: 10px solid red;
- margin: 20px;
- width: 200px; }
-
-body {
- margin: 0;
- padding: 3em 6em;
- font-family: tahoma, arial, sans-serif;
- color: #000;
-}
-
-@mixin yellow() {
- background: yellow;
-}
-
-.big {
- font-size: 14px;
-}
-
-.nested {
- @include border-radius(3px);
- @extend .big;
- p {
- background: whitesmoke;
- a {
- color: red;
- }
- }
-}
-
-#navigation a {
- font-weight: bold;
- text-decoration: none !important;
-}
-
-h1 {
- font-size: 2.5em;
-}
-
-h2 {
- font-size: 1.7em;
-}
-
-h1:before, h2:before {
- content: "::";
-}
-
-code {
- font-family: courier, monospace;
- font-size: 80%;
- color: #418A8A;
-}
-
-
-
- The SCSS mode is a sub-mode of the CSS mode (defined in css.js).
-
- Parsing/Highlighting Tests: normal , verbose .
-
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/scss_test.js b/js/DevHelper/Lib/CodeMirror/mode/css/scss_test.js
deleted file mode 100644
index 785921b..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/scss_test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss");
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }
-
- MT('url_with_quotation',
- "[tag foo] { [property background]:[atom url]([string test.jpg]) }");
-
- MT('url_with_double_quotes',
- "[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }");
-
- MT('url_with_single_quotes',
- "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }");
-
- MT('string',
- "[def @import] [string \"compass/css3\"]");
-
- MT('important_keyword',
- "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }");
-
- MT('variable',
- "[variable-2 $blue]:[atom #333]");
-
- MT('variable_as_attribute',
- "[tag foo] { [property color]:[variable-2 $blue] }");
-
- MT('numbers',
- "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }");
-
- MT('number_percentage',
- "[tag foo] { [property width]:[number 80%] }");
-
- MT('selector',
- "[builtin #hello][qualifier .world]{}");
-
- MT('singleline_comment',
- "[comment // this is a comment]");
-
- MT('multiline_comment',
- "[comment /*foobar*/]");
-
- MT('attribute_with_hyphen',
- "[tag foo] { [property font-size]:[number 10px] }");
-
- MT('string_after_attribute',
- "[tag foo] { [property content]:[string \"::\"] }");
-
- MT('directives',
- "[def @include] [qualifier .mixin]");
-
- MT('basic_structure',
- "[tag p] { [property background]:[keyword red]; }");
-
- MT('nested_structure',
- "[tag p] { [tag a] { [property color]:[keyword red]; } }");
-
- MT('mixin',
- "[def @mixin] [tag table-base] {}");
-
- MT('number_without_semicolon',
- "[tag p] {[property width]:[number 12]}",
- "[tag a] {[property color]:[keyword red];}");
-
- MT('atom_in_nested_block',
- "[tag p] { [tag a] { [property color]:[atom #000]; } }");
-
- MT('interpolation_in_property',
- "[tag foo] { #{[variable-2 $hello]}:[number 2]; }");
-
- MT('interpolation_in_selector',
- "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }");
-
- MT('interpolation_error',
- "[tag foo]#{[variable foo]} { [property color]:[atom #000]; }");
-
- MT("divide_operator",
- "[tag foo] { [property width]:[number 4] [operator /] [number 2] }");
-
- MT('nested_structure_with_id_selector',
- "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }");
-
- MT('indent_mixin',
- "[def @mixin] [tag container] (",
- " [variable-2 $a]: [number 10],",
- " [variable-2 $b]: [number 10])",
- "{}");
-
- MT('indent_nested',
- "[tag foo] {",
- " [tag bar] {",
- " }",
- "}");
-
- MT('indent_parentheses',
- "[tag foo] {",
- " [property color]: [atom darken]([variable-2 $blue],",
- " [number 9%]);",
- "}");
-
- MT('indent_vardef',
- "[variable-2 $name]:",
- " [string 'val'];",
- "[tag tag] {",
- " [tag inner] {",
- " [property margin]: [number 3px];",
- " }",
- "}");
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/css/test.js b/js/DevHelper/Lib/CodeMirror/mode/css/test.js
deleted file mode 100644
index 7a496fb..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/css/test.js
+++ /dev/null
@@ -1,200 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- var mode = CodeMirror.getMode({indentUnit: 2}, "css");
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
-
- // Error, because "foobarhello" is neither a known type or property, but
- // property was expected (after "and"), and it should be in parentheses.
- MT("atMediaUnknownType",
- "[def @media] [attribute screen] [keyword and] [error foobarhello] { }");
-
- // Soft error, because "foobarhello" is not a known property or type.
- MT("atMediaUnknownProperty",
- "[def @media] [attribute screen] [keyword and] ([error foobarhello]) { }");
-
- // Make sure nesting works with media queries
- MT("atMediaMaxWidthNested",
- "[def @media] [attribute screen] [keyword and] ([property max-width]: [number 25px]) { [tag foo] { } }");
-
- MT("atMediaFeatureValueKeyword",
- "[def @media] ([property orientation]: [keyword landscape]) { }");
-
- MT("atMediaUnknownFeatureValueKeyword",
- "[def @media] ([property orientation]: [error upsidedown]) { }");
-
- MT("tagSelector",
- "[tag foo] { }");
-
- MT("classSelector",
- "[qualifier .foo-bar_hello] { }");
-
- MT("idSelector",
- "[builtin #foo] { [error #foo] }");
-
- MT("tagSelectorUnclosed",
- "[tag foo] { [property margin]: [number 0] } [tag bar] { }");
-
- MT("tagStringNoQuotes",
- "[tag foo] { [property font-family]: [variable hello] [variable world]; }");
-
- MT("tagStringDouble",
- "[tag foo] { [property font-family]: [string \"hello world\"]; }");
-
- MT("tagStringSingle",
- "[tag foo] { [property font-family]: [string 'hello world']; }");
-
- MT("tagColorKeyword",
- "[tag foo] {",
- " [property color]: [keyword black];",
- " [property color]: [keyword navy];",
- " [property color]: [keyword yellow];",
- "}");
-
- MT("tagColorHex3",
- "[tag foo] { [property background]: [atom #fff]; }");
-
- MT("tagColorHex4",
- "[tag foo] { [property background]: [atom #ffff]; }");
-
- MT("tagColorHex6",
- "[tag foo] { [property background]: [atom #ffffff]; }");
-
- MT("tagColorHex8",
- "[tag foo] { [property background]: [atom #ffffffff]; }");
-
- MT("tagColorHex5Invalid",
- "[tag foo] { [property background]: [atom&error #fffff]; }");
-
- MT("tagColorHexInvalid",
- "[tag foo] { [property background]: [atom&error #ffg]; }");
-
- MT("tagNegativeNumber",
- "[tag foo] { [property margin]: [number -5px]; }");
-
- MT("tagPositiveNumber",
- "[tag foo] { [property padding]: [number 5px]; }");
-
- MT("tagVendor",
- "[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][atom border-box]; }");
-
- MT("tagBogusProperty",
- "[tag foo] { [property&error barhelloworld]: [number 0]; }");
-
- MT("tagTwoProperties",
- "[tag foo] { [property margin]: [number 0]; [property padding]: [number 0]; }");
-
- MT("tagTwoPropertiesURL",
- "[tag foo] { [property background]: [atom url]([string //example.com/foo.png]); [property padding]: [number 0]; }");
-
- MT("indent_tagSelector",
- "[tag strong], [tag em] {",
- " [property background]: [atom rgba](",
- " [number 255], [number 255], [number 0], [number .2]",
- " );",
- "}");
-
- MT("indent_atMedia",
- "[def @media] {",
- " [tag foo] {",
- " [property color]:",
- " [keyword yellow];",
- " }",
- "}");
-
- MT("indent_comma",
- "[tag foo] {",
- " [property font-family]: [variable verdana],",
- " [atom sans-serif];",
- "}");
-
- MT("indent_parentheses",
- "[tag foo]:[variable-3 before] {",
- " [property background]: [atom url](",
- "[string blahblah]",
- "[string etc]",
- "[string ]) [keyword !important];",
- "}");
-
- MT("font_face",
- "[def @font-face] {",
- " [property font-family]: [string 'myfont'];",
- " [error nonsense]: [string 'abc'];",
- " [property src]: [atom url]([string http://blah]),",
- " [atom url]([string http://foo]);",
- "}");
-
- MT("empty_url",
- "[def @import] [atom url]() [attribute screen];");
-
- MT("parens",
- "[qualifier .foo] {",
- " [property background-image]: [variable fade]([atom #000], [number 20%]);",
- " [property border-image]: [atom linear-gradient](",
- " [atom to] [atom bottom],",
- " [variable fade]([atom #000], [number 20%]) [number 0%],",
- " [variable fade]([atom #000], [number 20%]) [number 100%]",
- " );",
- "}");
-
- MT("css_variable",
- ":[variable-3 root] {",
- " [variable-2 --main-color]: [atom #06c];",
- "}",
- "[tag h1][builtin #foo] {",
- " [property color]: [atom var]([variable-2 --main-color]);",
- "}");
-
- MT("supports",
- "[def @supports] ([keyword not] (([property text-align-last]: [atom justify]) [keyword or] ([meta -moz-][property text-align-last]: [atom justify])) {",
- " [property text-align-last]: [atom justify];",
- "}");
-
- MT("document",
- "[def @document] [tag url]([string http://blah]),",
- " [tag url-prefix]([string https://]),",
- " [tag domain]([string blah.com]),",
- " [tag regexp]([string \".*blah.+\"]) {",
- " [builtin #id] {",
- " [property background-color]: [keyword white];",
- " }",
- " [tag foo] {",
- " [property font-family]: [variable Verdana], [atom sans-serif];",
- " }",
- "}");
-
- MT("document_url",
- "[def @document] [tag url]([string http://blah]) { [qualifier .class] { } }");
-
- MT("document_urlPrefix",
- "[def @document] [tag url-prefix]([string https://]) { [builtin #id] { } }");
-
- MT("document_domain",
- "[def @document] [tag domain]([string blah.com]) { [tag foo] { } }");
-
- MT("document_regexp",
- "[def @document] [tag regexp]([string \".*blah.+\"]) { [builtin #id] { } }");
-
- MT("counter-style",
- "[def @counter-style] [variable binary] {",
- " [property system]: [atom numeric];",
- " [property symbols]: [number 0] [number 1];",
- " [property suffix]: [string \".\"];",
- " [property range]: [atom infinite];",
- " [property speak-as]: [atom numeric];",
- "}");
-
- MT("counter-style-additive-symbols",
- "[def @counter-style] [variable simple-roman] {",
- " [property system]: [atom additive];",
- " [property additive-symbols]: [number 10] [variable X], [number 5] [variable V], [number 1] [variable I];",
- " [property range]: [number 1] [number 49];",
- "}");
-
- MT("counter-style-use",
- "[tag ol][qualifier .roman] { [property list-style]: [variable simple-roman]; }");
-
- MT("counter-style-symbols",
- "[tag ol] { [property list-style]: [atom symbols]([atom cyclic] [string \"*\"] [string \"\\2020\"] [string \"\\2021\"] [string \"\\A7\"]); }");
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/htmlembedded.js b/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/htmlembedded.js
deleted file mode 100644
index 464dc57..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/htmlembedded.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"),
- require("../../addon/mode/multiplex"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "../htmlmixed/htmlmixed",
- "../../addon/mode/multiplex"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
- return CodeMirror.multiplexingMode(CodeMirror.getMode(config, "htmlmixed"), {
- open: parserConfig.open || parserConfig.scriptStartRegex || "<%",
- close: parserConfig.close || parserConfig.scriptEndRegex || "%>",
- mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)
- });
- }, "htmlmixed");
-
- CodeMirror.defineMIME("application/x-ejs", {name: "htmlembedded", scriptingModeSpec:"javascript"});
- CodeMirror.defineMIME("application/x-aspx", {name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
- CodeMirror.defineMIME("application/x-jsp", {name: "htmlembedded", scriptingModeSpec:"text/x-java"});
- CodeMirror.defineMIME("application/x-erb", {name: "htmlembedded", scriptingModeSpec:"ruby"});
-});
diff --git a/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/index.html b/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/index.html
deleted file mode 100644
index 9ed33cf..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/htmlembedded/index.html
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-CodeMirror: Html Embedded Scripts mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Html Embedded Scripts mode
-
-<%
-function hello(who) {
- return "Hello " + who;
-}
-%>
-This is an example of EJS (embedded javascript)
-The program says <%= hello("world") %>.
-
-
-
-
-
- Mode for html embedded scripts like JSP and ASP.NET. Depends on multiplex and HtmlMixed which in turn depends on
- JavaScript, CSS and XML. Other dependencies include those of the scripting language chosen.
-
- MIME types defined: application/x-aspx (ASP.NET),
- application/x-ejs (Embedded Javascript), application/x-jsp (JavaServer Pages)
- and application/x-erb
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/htmlmixed.js b/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/htmlmixed.js
deleted file mode 100644
index 33398ec..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/htmlmixed.js
+++ /dev/null
@@ -1,152 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
- "use strict";
-
- var defaultTags = {
- script: [
- ["lang", /(javascript|babel)/i, "javascript"],
- ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
- ["type", /./, "text/plain"],
- [null, null, "javascript"]
- ],
- style: [
- ["lang", /^css$/i, "css"],
- ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
- ["type", /./, "text/plain"],
- [null, null, "css"]
- ]
- };
-
- function maybeBackup(stream, pat, style) {
- var cur = stream.current(), close = cur.search(pat);
- if (close > -1) {
- stream.backUp(cur.length - close);
- } else if (cur.match(/<\/?$/)) {
- stream.backUp(cur.length);
- if (!stream.match(pat, false)) stream.match(cur);
- }
- return style;
- }
-
- var attrRegexpCache = {};
- function getAttrRegexp(attr) {
- var regexp = attrRegexpCache[attr];
- if (regexp) return regexp;
- return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
- }
-
- function getAttrValue(text, attr) {
- var match = text.match(getAttrRegexp(attr))
- return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
- }
-
- function getTagRegexp(tagName, anchored) {
- return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
- }
-
- function addTags(from, to) {
- for (var tag in from) {
- var dest = to[tag] || (to[tag] = []);
- var source = from[tag];
- for (var i = source.length - 1; i >= 0; i--)
- dest.unshift(source[i])
- }
- }
-
- function findMatchingMode(tagInfo, tagText) {
- for (var i = 0; i < tagInfo.length; i++) {
- var spec = tagInfo[i];
- if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
- }
- }
-
- CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
- var htmlMode = CodeMirror.getMode(config, {
- name: "xml",
- htmlMode: true,
- multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
- multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
- });
-
- var tags = {};
- var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
- addTags(defaultTags, tags);
- if (configTags) addTags(configTags, tags);
- if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
- tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
-
- function html(stream, state) {
- var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
- if (tag && !/[<>\s\/]/.test(stream.current()) &&
- (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
- tags.hasOwnProperty(tagName)) {
- state.inTag = tagName + " "
- } else if (state.inTag && tag && />$/.test(stream.current())) {
- var inTag = /^([\S]+) (.*)/.exec(state.inTag)
- state.inTag = null
- var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
- var mode = CodeMirror.getMode(config, modeSpec)
- var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
- state.token = function (stream, state) {
- if (stream.match(endTagA, false)) {
- state.token = html;
- state.localState = state.localMode = null;
- return null;
- }
- return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
- };
- state.localMode = mode;
- state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, ""));
- } else if (state.inTag) {
- state.inTag += stream.current()
- if (stream.eol()) state.inTag += " "
- }
- return style;
- };
-
- return {
- startState: function () {
- var state = CodeMirror.startState(htmlMode);
- return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
- },
-
- copyState: function (state) {
- var local;
- if (state.localState) {
- local = CodeMirror.copyState(state.localMode, state.localState);
- }
- return {token: state.token, inTag: state.inTag,
- localMode: state.localMode, localState: local,
- htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
- },
-
- token: function (stream, state) {
- return state.token(stream, state);
- },
-
- indent: function (state, textAfter, line) {
- if (!state.localMode || /^\s*<\//.test(textAfter))
- return htmlMode.indent(state.htmlState, textAfter);
- else if (state.localMode.indent)
- return state.localMode.indent(state.localState, textAfter, line);
- else
- return CodeMirror.Pass;
- },
-
- innerMode: function (state) {
- return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
- }
- };
- }, "xml", "javascript", "css");
-
- CodeMirror.defineMIME("text/html", "htmlmixed");
-});
diff --git a/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/index.html b/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/index.html
deleted file mode 100644
index caa7546..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/htmlmixed/index.html
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-CodeMirror: HTML mixed mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-HTML mixed mode
-
-
-
-
- Mixed HTML Example
-
-
-
- Mixed HTML Example
-
-
-
-
-
-
- The HTML mixed mode depends on the XML, JavaScript, and CSS modes.
-
- It takes an optional mode configuration
- option, tags, which can be used to add custom
- behavior for specific tags. When given, it should be an object
- mapping tag names (for example script) to arrays or
- three-element arrays. Those inner arrays indicate [attributeName,
- valueRegexp, modeSpec ]
- specifications. For example, you could use ["type", /^foo$/,
- "foo"] to map the attribute type="foo" to
- the foo mode. When the first two fields are null
- ([null, null, "mode"]), the given mode is used for
- any such tag that doesn't match any of the previously given
- attributes. For example:
-
- var myModeSpec = {
- name: "htmlmixed",
- tags: {
- style: [["type", /^text/(x-)?scss$/, "text/x-scss"],
- [null, null, "css"]],
- custom: [[null, null, "customMode"]]
- }
-}
-
- MIME types defined: text/html
- (redefined, only takes effect if you load this parser after the
- XML parser).
-
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/javascript/index.html b/js/DevHelper/Lib/CodeMirror/mode/javascript/index.html
deleted file mode 100644
index 592a133..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/javascript/index.html
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-CodeMirror: JavaScript mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-JavaScript mode
-
-
-
-// Demo code (the actual new parser character stream implementation)
-
-function StringStream(string) {
- this.pos = 0;
- this.string = string;
-}
-
-StringStream.prototype = {
- done: function() {return this.pos >= this.string.length;},
- peek: function() {return this.string.charAt(this.pos);},
- next: function() {
- if (this.pos < this.string.length)
- return this.string.charAt(this.pos++);
- },
- eat: function(match) {
- var ch = this.string.charAt(this.pos);
- if (typeof match == "string") var ok = ch == match;
- else var ok = ch && match.test ? match.test(ch) : match(ch);
- if (ok) {this.pos++; return ch;}
- },
- eatWhile: function(match) {
- var start = this.pos;
- while (this.eat(match));
- if (this.pos > start) return this.string.slice(start, this.pos);
- },
- backUp: function(n) {this.pos -= n;},
- column: function() {return this.pos;},
- eatSpace: function() {
- var start = this.pos;
- while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
- return this.pos - start;
- },
- match: function(pattern, consume, caseInsensitive) {
- if (typeof pattern == "string") {
- function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
- if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
- if (consume !== false) this.pos += str.length;
- return true;
- }
- }
- else {
- var match = this.string.slice(this.pos).match(pattern);
- if (match && consume !== false) this.pos += match[0].length;
- return match;
- }
- }
-};
-
-
-
-
-
- JavaScript mode supports several configuration options:
-
- json which will set the mode to expect JSON
- data rather than a JavaScript program.
- jsonld which will set the mode to expect
- JSON-LD linked data rather
- than a JavaScript program (demo ).
- typescript which will activate additional
- syntax highlighting and some other things for TypeScript code
- (demo ).
- statementIndent which (given a number) will
- determine the amount of indentation to use for statements
- continued on a new line.
- wordCharacters, a regexp that indicates which
- characters should be considered part of an identifier.
- Defaults to /[\w$]/, which does not handle
- non-ASCII identifiers. Can be set to something more elaborate
- to improve Unicode support.
-
-
-
- MIME types defined: text/javascript, application/json, application/ld+json, text/typescript, application/typescript.
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/javascript/javascript.js b/js/DevHelper/Lib/CodeMirror/mode/javascript/javascript.js
deleted file mode 100644
index 692ffc6..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/javascript/javascript.js
+++ /dev/null
@@ -1,818 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-function expressionAllowed(stream, state, backUp) {
- return /^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
- (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
-}
-
-CodeMirror.defineMode("javascript", function(config, parserConfig) {
- var indentUnit = config.indentUnit;
- var statementIndent = parserConfig.statementIndent;
- var jsonldMode = parserConfig.jsonld;
- var jsonMode = parserConfig.json || jsonldMode;
- var isTS = parserConfig.typescript;
- var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
-
- // Tokenizer
-
- var keywords = function(){
- function kw(type) {return {type: type, style: "keyword"};}
- var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
- var operator = kw("operator"), atom = {type: "atom", style: "atom"};
-
- var jsKeywords = {
- "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
- "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
- "var": kw("var"), "const": kw("var"), "let": kw("var"),
- "function": kw("function"), "catch": kw("catch"),
- "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
- "in": operator, "typeof": operator, "instanceof": operator,
- "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
- "this": kw("this"), "class": kw("class"), "super": kw("atom"),
- "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
- "await": C
- };
-
- // Extend the 'normal' keywords with the TypeScript language extensions
- if (isTS) {
- var type = {type: "variable", style: "type"};
- var tsKeywords = {
- // object-like things
- "interface": kw("class"),
- "implements": C,
- "namespace": C,
- "module": kw("module"),
- "enum": kw("module"),
-
- // scope modifiers
- "public": kw("modifier"),
- "private": kw("modifier"),
- "protected": kw("modifier"),
- "abstract": kw("modifier"),
-
- // types
- "string": type, "number": type, "boolean": type, "any": type
- };
-
- for (var attr in tsKeywords) {
- jsKeywords[attr] = tsKeywords[attr];
- }
- }
-
- return jsKeywords;
- }();
-
- var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
- var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
-
- function readRegexp(stream) {
- var escaped = false, next, inSet = false;
- while ((next = stream.next()) != null) {
- if (!escaped) {
- if (next == "/" && !inSet) return;
- if (next == "[") inSet = true;
- else if (inSet && next == "]") inSet = false;
- }
- escaped = !escaped && next == "\\";
- }
- }
-
- // Used as scratch variables to communicate multiple values without
- // consing up tons of objects.
- var type, content;
- function ret(tp, style, cont) {
- type = tp; content = cont;
- return style;
- }
- function tokenBase(stream, state) {
- var ch = stream.next();
- if (ch == '"' || ch == "'") {
- state.tokenize = tokenString(ch);
- return state.tokenize(stream, state);
- } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
- return ret("number", "number");
- } else if (ch == "." && stream.match("..")) {
- return ret("spread", "meta");
- } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
- return ret(ch);
- } else if (ch == "=" && stream.eat(">")) {
- return ret("=>", "operator");
- } else if (ch == "0" && stream.eat(/x/i)) {
- stream.eatWhile(/[\da-f]/i);
- return ret("number", "number");
- } else if (ch == "0" && stream.eat(/o/i)) {
- stream.eatWhile(/[0-7]/i);
- return ret("number", "number");
- } else if (ch == "0" && stream.eat(/b/i)) {
- stream.eatWhile(/[01]/i);
- return ret("number", "number");
- } else if (/\d/.test(ch)) {
- stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
- return ret("number", "number");
- } else if (ch == "/") {
- if (stream.eat("*")) {
- state.tokenize = tokenComment;
- return tokenComment(stream, state);
- } else if (stream.eat("/")) {
- stream.skipToEnd();
- return ret("comment", "comment");
- } else if (expressionAllowed(stream, state, 1)) {
- readRegexp(stream);
- stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
- return ret("regexp", "string-2");
- } else {
- stream.eatWhile(isOperatorChar);
- return ret("operator", "operator", stream.current());
- }
- } else if (ch == "`") {
- state.tokenize = tokenQuasi;
- return tokenQuasi(stream, state);
- } else if (ch == "#") {
- stream.skipToEnd();
- return ret("error", "error");
- } else if (isOperatorChar.test(ch)) {
- if (ch != ">" || !state.lexical || state.lexical.type != ">")
- stream.eatWhile(isOperatorChar);
- return ret("operator", "operator", stream.current());
- } else if (wordRE.test(ch)) {
- stream.eatWhile(wordRE);
- var word = stream.current()
- if (state.lastType != ".") {
- if (keywords.propertyIsEnumerable(word)) {
- var kw = keywords[word]
- return ret(kw.type, kw.style, word)
- }
- if (word == "async" && stream.match(/^\s*[\(\w]/, false))
- return ret("async", "keyword", word)
- }
- return ret("variable", "variable", word)
- }
- }
-
- function tokenString(quote) {
- return function(stream, state) {
- var escaped = false, next;
- if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
- state.tokenize = tokenBase;
- return ret("jsonld-keyword", "meta");
- }
- while ((next = stream.next()) != null) {
- if (next == quote && !escaped) break;
- escaped = !escaped && next == "\\";
- }
- if (!escaped) state.tokenize = tokenBase;
- return ret("string", "string");
- };
- }
-
- function tokenComment(stream, state) {
- var maybeEnd = false, ch;
- while (ch = stream.next()) {
- if (ch == "/" && maybeEnd) {
- state.tokenize = tokenBase;
- break;
- }
- maybeEnd = (ch == "*");
- }
- return ret("comment", "comment");
- }
-
- function tokenQuasi(stream, state) {
- var escaped = false, next;
- while ((next = stream.next()) != null) {
- if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
- state.tokenize = tokenBase;
- break;
- }
- escaped = !escaped && next == "\\";
- }
- return ret("quasi", "string-2", stream.current());
- }
-
- var brackets = "([{}])";
- // This is a crude lookahead trick to try and notice that we're
- // parsing the argument patterns for a fat-arrow function before we
- // actually hit the arrow token. It only works if the arrow is on
- // the same line as the arguments and there's no strange noise
- // (comments) in between. Fallback is to only notice when we hit the
- // arrow, and not declare the arguments as locals for the arrow
- // body.
- function findFatArrow(stream, state) {
- if (state.fatArrowAt) state.fatArrowAt = null;
- var arrow = stream.string.indexOf("=>", stream.start);
- if (arrow < 0) return;
-
- if (isTS) { // Try to skip TypeScript return type declarations after the arguments
- var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
- if (m) arrow = m.index
- }
-
- var depth = 0, sawSomething = false;
- for (var pos = arrow - 1; pos >= 0; --pos) {
- var ch = stream.string.charAt(pos);
- var bracket = brackets.indexOf(ch);
- if (bracket >= 0 && bracket < 3) {
- if (!depth) { ++pos; break; }
- if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
- } else if (bracket >= 3 && bracket < 6) {
- ++depth;
- } else if (wordRE.test(ch)) {
- sawSomething = true;
- } else if (/["'\/]/.test(ch)) {
- return;
- } else if (sawSomething && !depth) {
- ++pos;
- break;
- }
- }
- if (sawSomething && !depth) state.fatArrowAt = pos;
- }
-
- // Parser
-
- var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
-
- function JSLexical(indented, column, type, align, prev, info) {
- this.indented = indented;
- this.column = column;
- this.type = type;
- this.prev = prev;
- this.info = info;
- if (align != null) this.align = align;
- }
-
- function inScope(state, varname) {
- for (var v = state.localVars; v; v = v.next)
- if (v.name == varname) return true;
- for (var cx = state.context; cx; cx = cx.prev) {
- for (var v = cx.vars; v; v = v.next)
- if (v.name == varname) return true;
- }
- }
-
- function parseJS(state, style, type, content, stream) {
- var cc = state.cc;
- // Communicate our context to the combinators.
- // (Less wasteful than consing up a hundred closures on every call.)
- cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
-
- if (!state.lexical.hasOwnProperty("align"))
- state.lexical.align = true;
-
- while(true) {
- var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
- if (combinator(type, content)) {
- while(cc.length && cc[cc.length - 1].lex)
- cc.pop()();
- if (cx.marked) return cx.marked;
- if (type == "variable" && inScope(state, content)) return "variable-2";
- return style;
- }
- }
- }
-
- // Combinator utils
-
- var cx = {state: null, column: null, marked: null, cc: null};
- function pass() {
- for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
- }
- function cont() {
- pass.apply(null, arguments);
- return true;
- }
- function register(varname) {
- function inList(list) {
- for (var v = list; v; v = v.next)
- if (v.name == varname) return true;
- return false;
- }
- var state = cx.state;
- cx.marked = "def";
- if (state.context) {
- if (inList(state.localVars)) return;
- state.localVars = {name: varname, next: state.localVars};
- } else {
- if (inList(state.globalVars)) return;
- if (parserConfig.globalVars)
- state.globalVars = {name: varname, next: state.globalVars};
- }
- }
-
- // Combinators
-
- var defaultVars = {name: "this", next: {name: "arguments"}};
- function pushcontext() {
- cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
- cx.state.localVars = defaultVars;
- }
- function popcontext() {
- cx.state.localVars = cx.state.context.vars;
- cx.state.context = cx.state.context.prev;
- }
- function pushlex(type, info) {
- var result = function() {
- var state = cx.state, indent = state.indented;
- if (state.lexical.type == "stat") indent = state.lexical.indented;
- else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
- indent = outer.indented;
- state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
- };
- result.lex = true;
- return result;
- }
- function poplex() {
- var state = cx.state;
- if (state.lexical.prev) {
- if (state.lexical.type == ")")
- state.indented = state.lexical.indented;
- state.lexical = state.lexical.prev;
- }
- }
- poplex.lex = true;
-
- function expect(wanted) {
- function exp(type) {
- if (type == wanted) return cont();
- else if (wanted == ";") return pass();
- else return cont(exp);
- };
- return exp;
- }
-
- function statement(type, value) {
- if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
- if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
- if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
- if (type == "{") return cont(pushlex("}"), block, poplex);
- if (type == ";") return cont();
- if (type == "if") {
- if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
- cx.state.cc.pop()();
- return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
- }
- if (type == "function") return cont(functiondef);
- if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
- if (type == "variable") {
- if (isTS && value == "type") {
- cx.marked = "keyword"
- return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
- } else {
- return cont(pushlex("stat"), maybelabel);
- }
- }
- if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"),
- block, poplex, poplex);
- if (type == "case") return cont(expression, expect(":"));
- if (type == "default") return cont(expect(":"));
- if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
- statement, poplex, popcontext);
- if (type == "class") return cont(pushlex("form"), className, poplex);
- if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
- if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
- if (type == "module") return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
- if (type == "async") return cont(statement)
- if (value == "@") return cont(expression, statement)
- return pass(pushlex("stat"), expression, expect(";"), poplex);
- }
- function expression(type) {
- return expressionInner(type, false);
- }
- function expressionNoComma(type) {
- return expressionInner(type, true);
- }
- function parenExpr(type) {
- if (type != "(") return pass()
- return cont(pushlex(")"), expression, expect(")"), poplex)
- }
- function expressionInner(type, noComma) {
- if (cx.state.fatArrowAt == cx.stream.start) {
- var body = noComma ? arrowBodyNoComma : arrowBody;
- if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
- else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
- }
-
- var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
- if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
- if (type == "function") return cont(functiondef, maybeop);
- if (type == "class") return cont(pushlex("form"), classExpression, poplex);
- if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
- if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
- if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
- if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
- if (type == "{") return contCommasep(objprop, "}", null, maybeop);
- if (type == "quasi") return pass(quasi, maybeop);
- if (type == "new") return cont(maybeTarget(noComma));
- return cont();
- }
- function maybeexpression(type) {
- if (type.match(/[;\}\)\],]/)) return pass();
- return pass(expression);
- }
- function maybeexpressionNoComma(type) {
- if (type.match(/[;\}\)\],]/)) return pass();
- return pass(expressionNoComma);
- }
-
- function maybeoperatorComma(type, value) {
- if (type == ",") return cont(expression);
- return maybeoperatorNoComma(type, value, false);
- }
- function maybeoperatorNoComma(type, value, noComma) {
- var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
- var expr = noComma == false ? expression : expressionNoComma;
- if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
- if (type == "operator") {
- if (/\+\+|--/.test(value)) return cont(me);
- if (value == "?") return cont(expression, expect(":"), expr);
- return cont(expr);
- }
- if (type == "quasi") { return pass(quasi, me); }
- if (type == ";") return;
- if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
- if (type == ".") return cont(property, me);
- if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
- if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
- }
- function quasi(type, value) {
- if (type != "quasi") return pass();
- if (value.slice(value.length - 2) != "${") return cont(quasi);
- return cont(expression, continueQuasi);
- }
- function continueQuasi(type) {
- if (type == "}") {
- cx.marked = "string-2";
- cx.state.tokenize = tokenQuasi;
- return cont(quasi);
- }
- }
- function arrowBody(type) {
- findFatArrow(cx.stream, cx.state);
- return pass(type == "{" ? statement : expression);
- }
- function arrowBodyNoComma(type) {
- findFatArrow(cx.stream, cx.state);
- return pass(type == "{" ? statement : expressionNoComma);
- }
- function maybeTarget(noComma) {
- return function(type) {
- if (type == ".") return cont(noComma ? targetNoComma : target);
- else return pass(noComma ? expressionNoComma : expression);
- };
- }
- function target(_, value) {
- if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
- }
- function targetNoComma(_, value) {
- if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
- }
- function maybelabel(type) {
- if (type == ":") return cont(poplex, statement);
- return pass(maybeoperatorComma, expect(";"), poplex);
- }
- function property(type) {
- if (type == "variable") {cx.marked = "property"; return cont();}
- }
- function objprop(type, value) {
- if (type == "async") {
- cx.marked = "property";
- return cont(objprop);
- } else if (type == "variable" || cx.style == "keyword") {
- cx.marked = "property";
- if (value == "get" || value == "set") return cont(getterSetter);
- return cont(afterprop);
- } else if (type == "number" || type == "string") {
- cx.marked = jsonldMode ? "property" : (cx.style + " property");
- return cont(afterprop);
- } else if (type == "jsonld-keyword") {
- return cont(afterprop);
- } else if (type == "modifier") {
- return cont(objprop)
- } else if (type == "[") {
- return cont(expression, expect("]"), afterprop);
- } else if (type == "spread") {
- return cont(expression, afterprop);
- } else if (type == ":") {
- return pass(afterprop)
- }
- }
- function getterSetter(type) {
- if (type != "variable") return pass(afterprop);
- cx.marked = "property";
- return cont(functiondef);
- }
- function afterprop(type) {
- if (type == ":") return cont(expressionNoComma);
- if (type == "(") return pass(functiondef);
- }
- function commasep(what, end, sep) {
- function proceed(type, value) {
- if (sep ? sep.indexOf(type) > -1 : type == ",") {
- var lex = cx.state.lexical;
- if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
- return cont(function(type, value) {
- if (type == end || value == end) return pass()
- return pass(what)
- }, proceed);
- }
- if (type == end || value == end) return cont();
- return cont(expect(end));
- }
- return function(type, value) {
- if (type == end || value == end) return cont();
- return pass(what, proceed);
- };
- }
- function contCommasep(what, end, info) {
- for (var i = 3; i < arguments.length; i++)
- cx.cc.push(arguments[i]);
- return cont(pushlex(end, info), commasep(what, end), poplex);
- }
- function block(type) {
- if (type == "}") return cont();
- return pass(statement, block);
- }
- function maybetype(type, value) {
- if (isTS) {
- if (type == ":") return cont(typeexpr);
- if (value == "?") return cont(maybetype);
- }
- }
- function typeexpr(type) {
- if (type == "variable") {cx.marked = "type"; return cont(afterType);}
- if (type == "string" || type == "number" || type == "atom") return cont(afterType);
- if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
- if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
- }
- function maybeReturnType(type) {
- if (type == "=>") return cont(typeexpr)
- }
- function typeprop(type, value) {
- if (type == "variable" || cx.style == "keyword") {
- cx.marked = "property"
- return cont(typeprop)
- } else if (value == "?") {
- return cont(typeprop)
- } else if (type == ":") {
- return cont(typeexpr)
- } else if (type == "[") {
- return cont(expression, maybetype, expect("]"), typeprop)
- }
- }
- function typearg(type) {
- if (type == "variable") return cont(typearg)
- else if (type == ":") return cont(typeexpr)
- }
- function afterType(type, value) {
- if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
- if (value == "|" || type == ".") return cont(typeexpr)
- if (type == "[") return cont(expect("]"), afterType)
- if (value == "extends") return cont(typeexpr)
- }
- function vardef() {
- return pass(pattern, maybetype, maybeAssign, vardefCont);
- }
- function pattern(type, value) {
- if (type == "modifier") return cont(pattern)
- if (type == "variable") { register(value); return cont(); }
- if (type == "spread") return cont(pattern);
- if (type == "[") return contCommasep(pattern, "]");
- if (type == "{") return contCommasep(proppattern, "}");
- }
- function proppattern(type, value) {
- if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
- register(value);
- return cont(maybeAssign);
- }
- if (type == "variable") cx.marked = "property";
- if (type == "spread") return cont(pattern);
- if (type == "}") return pass();
- return cont(expect(":"), pattern, maybeAssign);
- }
- function maybeAssign(_type, value) {
- if (value == "=") return cont(expressionNoComma);
- }
- function vardefCont(type) {
- if (type == ",") return cont(vardef);
- }
- function maybeelse(type, value) {
- if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
- }
- function forspec(type) {
- if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
- }
- function forspec1(type) {
- if (type == "var") return cont(vardef, expect(";"), forspec2);
- if (type == ";") return cont(forspec2);
- if (type == "variable") return cont(formaybeinof);
- return pass(expression, expect(";"), forspec2);
- }
- function formaybeinof(_type, value) {
- if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
- return cont(maybeoperatorComma, forspec2);
- }
- function forspec2(type, value) {
- if (type == ";") return cont(forspec3);
- if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
- return pass(expression, expect(";"), forspec3);
- }
- function forspec3(type) {
- if (type != ")") cont(expression);
- }
- function functiondef(type, value) {
- if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
- if (type == "variable") {register(value); return cont(functiondef);}
- if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
- if (isTS && value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, functiondef)
- }
- function funarg(type) {
- if (type == "spread") return cont(funarg);
- return pass(pattern, maybetype, maybeAssign);
- }
- function classExpression(type, value) {
- // Class expressions may have an optional name.
- if (type == "variable") return className(type, value);
- return classNameAfter(type, value);
- }
- function className(type, value) {
- if (type == "variable") {register(value); return cont(classNameAfter);}
- }
- function classNameAfter(type, value) {
- if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, classNameAfter)
- if (value == "extends" || value == "implements" || (isTS && type == ","))
- return cont(isTS ? typeexpr : expression, classNameAfter);
- if (type == "{") return cont(pushlex("}"), classBody, poplex);
- }
- function classBody(type, value) {
- if (type == "variable" || cx.style == "keyword") {
- if ((value == "async" || value == "static" || value == "get" || value == "set" ||
- (isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) &&
- cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) {
- cx.marked = "keyword";
- return cont(classBody);
- }
- cx.marked = "property";
- return cont(isTS ? classfield : functiondef, classBody);
- }
- if (type == "[")
- return cont(expression, expect("]"), isTS ? classfield : functiondef, classBody)
- if (value == "*") {
- cx.marked = "keyword";
- return cont(classBody);
- }
- if (type == ";") return cont(classBody);
- if (type == "}") return cont();
- if (value == "@") return cont(expression, classBody)
- }
- function classfield(type, value) {
- if (value == "?") return cont(classfield)
- if (type == ":") return cont(typeexpr, maybeAssign)
- if (value == "=") return cont(expressionNoComma)
- return pass(functiondef)
- }
- function afterExport(type, value) {
- if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
- if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
- if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
- return pass(statement);
- }
- function exportField(type, value) {
- if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
- if (type == "variable") return pass(expressionNoComma, exportField);
- }
- function afterImport(type) {
- if (type == "string") return cont();
- return pass(importSpec, maybeMoreImports, maybeFrom);
- }
- function importSpec(type, value) {
- if (type == "{") return contCommasep(importSpec, "}");
- if (type == "variable") register(value);
- if (value == "*") cx.marked = "keyword";
- return cont(maybeAs);
- }
- function maybeMoreImports(type) {
- if (type == ",") return cont(importSpec, maybeMoreImports)
- }
- function maybeAs(_type, value) {
- if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
- }
- function maybeFrom(_type, value) {
- if (value == "from") { cx.marked = "keyword"; return cont(expression); }
- }
- function arrayLiteral(type) {
- if (type == "]") return cont();
- return pass(commasep(expressionNoComma, "]"));
- }
-
- function isContinuedStatement(state, textAfter) {
- return state.lastType == "operator" || state.lastType == "," ||
- isOperatorChar.test(textAfter.charAt(0)) ||
- /[,.]/.test(textAfter.charAt(0));
- }
-
- // Interface
-
- return {
- startState: function(basecolumn) {
- var state = {
- tokenize: tokenBase,
- lastType: "sof",
- cc: [],
- lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
- localVars: parserConfig.localVars,
- context: parserConfig.localVars && {vars: parserConfig.localVars},
- indented: basecolumn || 0
- };
- if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
- state.globalVars = parserConfig.globalVars;
- return state;
- },
-
- token: function(stream, state) {
- if (stream.sol()) {
- if (!state.lexical.hasOwnProperty("align"))
- state.lexical.align = false;
- state.indented = stream.indentation();
- findFatArrow(stream, state);
- }
- if (state.tokenize != tokenComment && stream.eatSpace()) return null;
- var style = state.tokenize(stream, state);
- if (type == "comment") return style;
- state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
- return parseJS(state, style, type, content, stream);
- },
-
- indent: function(state, textAfter) {
- if (state.tokenize == tokenComment) return CodeMirror.Pass;
- if (state.tokenize != tokenBase) return 0;
- var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
- // Kludge to prevent 'maybelse' from blocking lexical scope pops
- if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
- var c = state.cc[i];
- if (c == poplex) lexical = lexical.prev;
- else if (c != maybeelse) break;
- }
- while ((lexical.type == "stat" || lexical.type == "form") &&
- (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
- (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
- !/^[,\.=+\-*:?[\(]/.test(textAfter))))
- lexical = lexical.prev;
- if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
- lexical = lexical.prev;
- var type = lexical.type, closing = firstChar == type;
-
- if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
- else if (type == "form" && firstChar == "{") return lexical.indented;
- else if (type == "form") return lexical.indented + indentUnit;
- else if (type == "stat")
- return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
- else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
- return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
- else if (lexical.align) return lexical.column + (closing ? 0 : 1);
- else return lexical.indented + (closing ? 0 : indentUnit);
- },
-
- electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
- blockCommentStart: jsonMode ? null : "/*",
- blockCommentEnd: jsonMode ? null : "*/",
- lineComment: jsonMode ? null : "//",
- fold: "brace",
- closeBrackets: "()[]{}''\"\"``",
-
- helperType: jsonMode ? "json" : "javascript",
- jsonldMode: jsonldMode,
- jsonMode: jsonMode,
-
- expressionAllowed: expressionAllowed,
- skipExpression: function(state) {
- var top = state.cc[state.cc.length - 1]
- if (top == expression || top == expressionNoComma) state.cc.pop()
- }
- };
-});
-
-CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
-
-CodeMirror.defineMIME("text/javascript", "javascript");
-CodeMirror.defineMIME("text/ecmascript", "javascript");
-CodeMirror.defineMIME("application/javascript", "javascript");
-CodeMirror.defineMIME("application/x-javascript", "javascript");
-CodeMirror.defineMIME("application/ecmascript", "javascript");
-CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
-CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
-CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
-
-});
diff --git a/js/DevHelper/Lib/CodeMirror/mode/javascript/json-ld.html b/js/DevHelper/Lib/CodeMirror/mode/javascript/json-ld.html
deleted file mode 100644
index 3a37f0b..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/javascript/json-ld.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-CodeMirror: JSON-LD mode
-
-
-
-
-
-
-
-
-
-
-
-
-
-JSON-LD mode
-
-
-
-{
- "@context": {
- "name": "http://schema.org/name",
- "description": "http://schema.org/description",
- "image": {
- "@id": "http://schema.org/image",
- "@type": "@id"
- },
- "geo": "http://schema.org/geo",
- "latitude": {
- "@id": "http://schema.org/latitude",
- "@type": "xsd:float"
- },
- "longitude": {
- "@id": "http://schema.org/longitude",
- "@type": "xsd:float"
- },
- "xsd": "http://www.w3.org/2001/XMLSchema#"
- },
- "name": "The Empire State Building",
- "description": "The Empire State Building is a 102-story landmark in New York City.",
- "image": "http://www.civil.usherbrooke.ca/cours/gci215a/empire-state-building.jpg",
- "geo": {
- "latitude": "40.75",
- "longitude": "73.98"
- }
-}
-
-
-
-
- This is a specialization of the JavaScript mode .
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/javascript/test.js b/js/DevHelper/Lib/CodeMirror/mode/javascript/test.js
deleted file mode 100644
index 328cbc2..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/javascript/test.js
+++ /dev/null
@@ -1,379 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- var mode = CodeMirror.getMode({indentUnit: 2}, "javascript");
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
-
- MT("locals",
- "[keyword function] [def foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }");
-
- MT("comma-and-binop",
- "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }");
-
- MT("destructuring",
- "([keyword function]([def a], [[[def b], [def c] ]]) {",
- " [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);",
- " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];",
- "})();");
-
- MT("destructure_trailing_comma",
- "[keyword let] {[def a], [def b],} [operator =] [variable foo];",
- "[keyword let] [def c];"); // Parser still in good state?
-
- MT("class_body",
- "[keyword class] [def Foo] {",
- " [property constructor]() {}",
- " [property sayName]() {",
- " [keyword return] [string-2 `foo${][variable foo][string-2 }oo`];",
- " }",
- "}");
-
- MT("class",
- "[keyword class] [def Point] [keyword extends] [variable SuperThing] {",
- " [keyword get] [property prop]() { [keyword return] [number 24]; }",
- " [property constructor]([def x], [def y]) {",
- " [keyword super]([string 'something']);",
- " [keyword this].[property x] [operator =] [variable-2 x];",
- " }",
- "}");
-
- MT("anonymous_class_expression",
- "[keyword const] [def Adder] [operator =] [keyword class] [keyword extends] [variable Arithmetic] {",
- " [property add]([def a], [def b]) {}",
- "};");
-
- MT("named_class_expression",
- "[keyword const] [def Subber] [operator =] [keyword class] [def Subtract] {",
- " [property sub]([def a], [def b]) {}",
- "};");
-
- MT("class_async_method",
- "[keyword class] [def Foo] {",
- " [property sayName1]() {}",
- " [keyword async] [property sayName2]() {}",
- "}");
-
- MT("import",
- "[keyword function] [def foo]() {",
- " [keyword import] [def $] [keyword from] [string 'jquery'];",
- " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];",
- "}");
-
- MT("import_trailing_comma",
- "[keyword import] {[def foo], [def bar],} [keyword from] [string 'baz']")
-
- MT("const",
- "[keyword function] [def f]() {",
- " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];",
- "}");
-
- MT("for/of",
- "[keyword for]([keyword let] [def of] [keyword of] [variable something]) {}");
-
- MT("generator",
- "[keyword function*] [def repeat]([def n]) {",
- " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])",
- " [keyword yield] [variable-2 i];",
- "}");
-
- MT("quotedStringAddition",
- "[keyword let] [def f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];");
-
- MT("quotedFatArrow",
- "[keyword let] [def f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];");
-
- MT("fatArrow",
- "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);",
- "[variable a];", // No longer in scope
- "[keyword let] [def f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];",
- "[variable c];");
-
- MT("spread",
- "[keyword function] [def f]([def a], [meta ...][def b]) {",
- " [variable something]([variable-2 a], [meta ...][variable-2 b]);",
- "}");
-
- MT("quasi",
- "[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]");
-
- MT("quasi_no_function",
- "[variable x] [operator =] [string-2 `fofdlakj${][variable x] [operator +] [string-2 `foo`] [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]");
-
- MT("indent_statement",
- "[keyword var] [def x] [operator =] [number 10]",
- "[variable x] [operator +=] [variable y] [operator +]",
- " [atom Infinity]",
- "[keyword debugger];");
-
- MT("indent_if",
- "[keyword if] ([number 1])",
- " [keyword break];",
- "[keyword else] [keyword if] ([number 2])",
- " [keyword continue];",
- "[keyword else]",
- " [number 10];",
- "[keyword if] ([number 1]) {",
- " [keyword break];",
- "} [keyword else] [keyword if] ([number 2]) {",
- " [keyword continue];",
- "} [keyword else] {",
- " [number 10];",
- "}");
-
- MT("indent_for",
- "[keyword for] ([keyword var] [def i] [operator =] [number 0];",
- " [variable i] [operator <] [number 100];",
- " [variable i][operator ++])",
- " [variable doSomething]([variable i]);",
- "[keyword debugger];");
-
- MT("indent_c_style",
- "[keyword function] [def foo]()",
- "{",
- " [keyword debugger];",
- "}");
-
- MT("indent_else",
- "[keyword for] (;;)",
- " [keyword if] ([variable foo])",
- " [keyword if] ([variable bar])",
- " [number 1];",
- " [keyword else]",
- " [number 2];",
- " [keyword else]",
- " [number 3];");
-
- MT("indent_funarg",
- "[variable foo]([number 10000],",
- " [keyword function]([def a]) {",
- " [keyword debugger];",
- "};");
-
- MT("indent_below_if",
- "[keyword for] (;;)",
- " [keyword if] ([variable foo])",
- " [number 1];",
- "[number 2];");
-
- MT("indent_semicolonless_if",
- "[keyword function] [def foo]() {",
- " [keyword if] ([variable x])",
- " [variable foo]()",
- "}")
-
- MT("indent_semicolonless_if_with_statement",
- "[keyword function] [def foo]() {",
- " [keyword if] ([variable x])",
- " [variable foo]()",
- " [variable bar]()",
- "}")
-
- MT("multilinestring",
- "[keyword var] [def x] [operator =] [string 'foo\\]",
- "[string bar'];");
-
- MT("scary_regexp",
- "[string-2 /foo[[/]]bar/];");
-
- MT("indent_strange_array",
- "[keyword var] [def x] [operator =] [[",
- " [number 1],,",
- " [number 2],",
- "]];",
- "[number 10];");
-
- MT("param_default",
- "[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {",
- " [keyword return] [variable-2 x];",
- "}");
-
- MT("new_target",
- "[keyword function] [def F]([def target]) {",
- " [keyword if] ([variable-2 target] [operator &&] [keyword new].[keyword target].[property name]) {",
- " [keyword return] [keyword new]",
- " .[keyword target];",
- " }",
- "}");
-
- MT("async",
- "[keyword async] [keyword function] [def foo]([def args]) { [keyword return] [atom true]; }");
-
- MT("async_assignment",
- "[keyword const] [def foo] [operator =] [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; };");
-
- MT("async_object",
- "[keyword let] [def obj] [operator =] { [property async]: [atom false] };");
-
- // async be highlighet as keyword and foo as def, but it requires potentially expensive look-ahead. See #4173
- MT("async_object_function",
- "[keyword let] [def obj] [operator =] { [property async] [property foo]([def args]) { [keyword return] [atom true]; } };");
-
- MT("async_object_properties",
- "[keyword let] [def obj] [operator =] {",
- " [property prop1]: [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; },",
- " [property prop2]: [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; },",
- " [property prop3]: [keyword async] [keyword function] [def prop3]([def args]) { [keyword return] [atom true]; },",
- "};");
-
- MT("async_arrow",
- "[keyword const] [def foo] [operator =] [keyword async] ([def args]) [operator =>] { [keyword return] [atom true]; };");
-
- MT("async_jquery",
- "[variable $].[property ajax]({",
- " [property url]: [variable url],",
- " [property async]: [atom true],",
- " [property method]: [string 'GET']",
- "});");
-
- MT("async_variable",
- "[keyword const] [def async] [operator =] {[property a]: [number 1]};",
- "[keyword const] [def foo] [operator =] [string-2 `bar ${][variable async].[property a][string-2 }`];")
-
- MT("indent_switch",
- "[keyword switch] ([variable x]) {",
- " [keyword default]:",
- " [keyword return] [number 2]",
- "}")
-
- var ts_mode = CodeMirror.getMode({indentUnit: 2}, "application/typescript")
- function TS(name) {
- test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1))
- }
-
- TS("typescript_extend_type",
- "[keyword class] [def Foo] [keyword extends] [type Some][operator <][type Type][operator >] {}")
-
- TS("typescript_arrow_type",
- "[keyword let] [def x]: ([variable arg]: [type Type]) [operator =>] [type ReturnType]")
-
- TS("typescript_class",
- "[keyword class] [def Foo] {",
- " [keyword public] [keyword static] [property main]() {}",
- " [keyword private] [property _foo]: [type string];",
- "}")
-
- TS("typescript_literal_types",
- "[keyword import] [keyword *] [keyword as] [def Sequelize] [keyword from] [string 'sequelize'];",
- "[keyword interface] [def MyAttributes] {",
- " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];",
- " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];",
- "}",
- "[keyword interface] [def MyInstance] [keyword extends] [type Sequelize].[type Instance] [operator <] [type MyAttributes] [operator >] {",
- " [property rawAttributes]: [type MyAttributes];",
- " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];",
- " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];",
- "}")
-
- TS("typescript_extend_operators",
- "[keyword export] [keyword interface] [def UserModel] [keyword extends]",
- " [type Sequelize].[type Model] [operator <] [type UserInstance], [type UserAttributes] [operator >] {",
- " [property findById]: (",
- " [variable userId]: [type number]",
- " ) [operator =>] [type Promise] [operator <] [type Array] [operator <] { [property id], [property name] } [operator >>];",
- " [property updateById]: (",
- " [variable userId]: [type number],",
- " [variable isActive]: [type boolean]",
- " ) [operator =>] [type Promise] [operator <] [type AccountHolderNotificationPreferenceInstance] [operator >];",
- " }")
-
- TS("typescript_interface_with_const",
- "[keyword const] [def hello]: {",
- " [property prop1][operator ?]: [type string];",
- " [property prop2][operator ?]: [type string];",
- "} [operator =] {};")
-
- TS("typescript_double_extend",
- "[keyword export] [keyword interface] [def UserAttributes] {",
- " [property id][operator ?]: [type number];",
- " [property createdAt][operator ?]: [type Date];",
- "}",
- "[keyword export] [keyword interface] [def UserInstance] [keyword extends] [type Sequelize].[type Instance][operator <][type UserAttributes][operator >], [type UserAttributes] {",
- " [property id]: [type number];",
- " [property createdAt]: [type Date];",
- "}");
-
- TS("typescript_index_signature",
- "[keyword interface] [def A] {",
- " [[ [variable prop]: [type string] ]]: [type any];",
- " [property prop1]: [type any];",
- "}");
-
- TS("typescript_generic_class",
- "[keyword class] [def Foo][operator <][type T][operator >] {",
- " [property bar]() {}",
- " [property foo](): [type Foo] {}",
- "}")
-
- TS("typescript_type_when_keyword",
- "[keyword export] [keyword type] [type AB] [operator =] [type A] [operator |] [type B];",
- "[keyword type] [type Flags] [operator =] {",
- " [property p1]: [type string];",
- " [property p2]: [type boolean];",
- "};")
-
- TS("typescript_type_when_not_keyword",
- "[keyword class] [def HasType] {",
- " [property type]: [type string];",
- " [property constructor]([def type]: [type string]) {",
- " [keyword this].[property type] [operator =] [variable-2 type];",
- " }",
- " [property setType]({ [def type] }: { [property type]: [type string]; }) {",
- " [keyword this].[property type] [operator =] [variable-2 type];",
- " }",
- "}")
-
- TS("typescript_function_generics",
- "[keyword function] [def a]() {}",
- "[keyword function] [def b][operator <][type IA] [keyword extends] [type object], [type IB] [keyword extends] [type object][operator >]() {}",
- "[keyword function] [def c]() {}")
-
- TS("typescript_complex_return_type",
- "[keyword function] [def A]() {",
- " [keyword return] [keyword this].[property property];",
- "}",
- "[keyword function] [def B](): [type Promise][operator <]{ [[ [variable key]: [type string] ]]: [type any] } [operator |] [atom null][operator >] {",
- " [keyword return] [keyword this].[property property];",
- "}")
-
- TS("typescript_complex_type_casting",
- "[keyword const] [def giftpay] [operator =] [variable config].[property get]([string 'giftpay']) [keyword as] { [[ [variable platformUuid]: [type string] ]]: { [property version]: [type number]; [property apiCode]: [type string]; } };")
-
- var jsonld_mode = CodeMirror.getMode(
- {indentUnit: 2},
- {name: "javascript", jsonld: true}
- );
- function LD(name) {
- test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1));
- }
-
- LD("json_ld_keywords",
- '{',
- ' [meta "@context"]: {',
- ' [meta "@base"]: [string "http://example.com"],',
- ' [meta "@vocab"]: [string "http://xmlns.com/foaf/0.1/"],',
- ' [property "likesFlavor"]: {',
- ' [meta "@container"]: [meta "@list"]',
- ' [meta "@reverse"]: [string "@beFavoriteOf"]',
- ' },',
- ' [property "nick"]: { [meta "@container"]: [meta "@set"] },',
- ' [property "nick"]: { [meta "@container"]: [meta "@index"] }',
- ' },',
- ' [meta "@graph"]: [[ {',
- ' [meta "@id"]: [string "http://dbpedia.org/resource/John_Lennon"],',
- ' [property "name"]: [string "John Lennon"],',
- ' [property "modified"]: {',
- ' [meta "@value"]: [string "2010-05-29T14:17:39+02:00"],',
- ' [meta "@type"]: [string "http://www.w3.org/2001/XMLSchema#dateTime"]',
- ' }',
- ' } ]]',
- '}');
-
- LD("json_ld_fake",
- '{',
- ' [property "@fake"]: [string "@fake"],',
- ' [property "@contextual"]: [string "@identifier"],',
- ' [property "user@domain.com"]: [string "@graphical"],',
- ' [property "@ID"]: [string "@@ID"]',
- '}');
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/javascript/typescript.html b/js/DevHelper/Lib/CodeMirror/mode/javascript/typescript.html
deleted file mode 100644
index 1f26d7f..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/javascript/typescript.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-CodeMirror: TypeScript mode
-
-
-
-
-
-
-
-
-
-
-TypeScript mode
-
-
-
-class Greeter {
- greeting: string;
- constructor (message: string) {
- this.greeting = message;
- }
- greet() {
- return "Hello, " + this.greeting;
- }
-}
-
-var greeter = new Greeter("world");
-
-var button = document.createElement('button')
-button.innerText = "Say Hello"
-button.onclick = function() {
- alert(greeter.greet())
-}
-
-document.body.appendChild(button)
-
-
-
-
-
- This is a specialization of the JavaScript mode .
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/xml/index.html b/js/DevHelper/Lib/CodeMirror/mode/xml/index.html
deleted file mode 100644
index c56b8b6..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/xml/index.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-CodeMirror: XML mode
-
-
-
-
-
-
-
-
-
-
-XML mode
-
-<html style="color: green">
- <!-- this is a comment -->
- <head>
- <title>HTML Example</title>
- </head>
- <body>
- The indentation tries to be <em>somewhat "do what
- I mean"</em>... but might not match your style.
- </body>
-</html>
-
-
- The XML mode supports these configuration parameters:
-
- htmlMode (boolean)
- This switches the mode to parse HTML instead of XML. This
- means attributes do not have to be quoted, and some elements
- (such as br) do not require a closing tag.
- matchClosing (boolean)
- Controls whether the mode checks that close tags match the
- corresponding opening tag, and highlights mismatches as errors.
- Defaults to true.
- alignCDATA (boolean)
- Setting this to true will force the opening tag of CDATA
- blocks to not be indented.
-
-
- MIME types defined: application/xml, text/html.
-
diff --git a/js/DevHelper/Lib/CodeMirror/mode/xml/test.js b/js/DevHelper/Lib/CodeMirror/mode/xml/test.js
deleted file mode 100644
index f48156b..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/xml/test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
- var mode = CodeMirror.getMode({indentUnit: 2}, "xml"), mname = "xml";
- function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), mname); }
-
- MT("matching",
- "[tag&bracket <][tag top][tag&bracket >]",
- " text",
- " [tag&bracket <][tag inner][tag&bracket />]",
- "[tag&bracket ][tag top][tag&bracket >]");
-
- MT("nonmatching",
- "[tag&bracket <][tag top][tag&bracket >]",
- " [tag&bracket <][tag inner][tag&bracket />]",
- " [tag&bracket ][tag&error tip][tag&bracket&error >]");
-
- MT("doctype",
- "[meta ]",
- "[tag&bracket <][tag top][tag&bracket />]");
-
- MT("cdata",
- "[tag&bracket <][tag top][tag&bracket >]",
- " [atom ]",
- "[tag&bracket ][tag top][tag&bracket >]");
-
- // HTML tests
- mode = CodeMirror.getMode({indentUnit: 2}, "text/html");
-
- MT("selfclose",
- "[tag&bracket <][tag html][tag&bracket >]",
- " [tag&bracket <][tag link] [attribute rel]=[string stylesheet] [attribute href]=[string \"/foobar\"][tag&bracket >]",
- "[tag&bracket ][tag html][tag&bracket >]");
-
- MT("list",
- "[tag&bracket <][tag ol][tag&bracket >]",
- " [tag&bracket <][tag li][tag&bracket >]one",
- " [tag&bracket <][tag li][tag&bracket >]two",
- "[tag&bracket ][tag ol][tag&bracket >]");
-
- MT("valueless",
- "[tag&bracket <][tag input] [attribute type]=[string checkbox] [attribute checked][tag&bracket />]");
-
- MT("pThenArticle",
- "[tag&bracket <][tag p][tag&bracket >]",
- " foo",
- "[tag&bracket <][tag article][tag&bracket >]bar");
-
-})();
diff --git a/js/DevHelper/Lib/CodeMirror/mode/xml/xml.js b/js/DevHelper/Lib/CodeMirror/mode/xml/xml.js
deleted file mode 100644
index f987a3a..0000000
--- a/js/DevHelper/Lib/CodeMirror/mode/xml/xml.js
+++ /dev/null
@@ -1,394 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
- else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-var htmlConfig = {
- autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
- 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
- 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
- 'track': true, 'wbr': true, 'menuitem': true},
- implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
- 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
- 'th': true, 'tr': true},
- contextGrabbers: {
- 'dd': {'dd': true, 'dt': true},
- 'dt': {'dd': true, 'dt': true},
- 'li': {'li': true},
- 'option': {'option': true, 'optgroup': true},
- 'optgroup': {'optgroup': true},
- 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
- 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
- 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
- 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
- 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
- 'rp': {'rp': true, 'rt': true},
- 'rt': {'rp': true, 'rt': true},
- 'tbody': {'tbody': true, 'tfoot': true},
- 'td': {'td': true, 'th': true},
- 'tfoot': {'tbody': true},
- 'th': {'td': true, 'th': true},
- 'thead': {'tbody': true, 'tfoot': true},
- 'tr': {'tr': true}
- },
- doNotIndent: {"pre": true},
- allowUnquoted: true,
- allowMissing: true,
- caseFold: true
-}
-
-var xmlConfig = {
- autoSelfClosers: {},
- implicitlyClosed: {},
- contextGrabbers: {},
- doNotIndent: {},
- allowUnquoted: false,
- allowMissing: false,
- caseFold: false
-}
-
-CodeMirror.defineMode("xml", function(editorConf, config_) {
- var indentUnit = editorConf.indentUnit
- var config = {}
- var defaults = config_.htmlMode ? htmlConfig : xmlConfig
- for (var prop in defaults) config[prop] = defaults[prop]
- for (var prop in config_) config[prop] = config_[prop]
-
- // Return variables for tokenizers
- var type, setStyle;
-
- function inText(stream, state) {
- function chain(parser) {
- state.tokenize = parser;
- return parser(stream, state);
- }
-
- var ch = stream.next();
- if (ch == "<") {
- if (stream.eat("!")) {
- if (stream.eat("[")) {
- if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
- else return null;
- } else if (stream.match("--")) {
- return chain(inBlock("comment", "-->"));
- } else if (stream.match("DOCTYPE", true, true)) {
- stream.eatWhile(/[\w\._\-]/);
- return chain(doctype(1));
- } else {
- return null;
- }
- } else if (stream.eat("?")) {
- stream.eatWhile(/[\w\._\-]/);
- state.tokenize = inBlock("meta", "?>");
- return "meta";
- } else {
- type = stream.eat("/") ? "closeTag" : "openTag";
- state.tokenize = inTag;
- return "tag bracket";
- }
- } else if (ch == "&") {
- var ok;
- if (stream.eat("#")) {
- if (stream.eat("x")) {
- ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
- } else {
- ok = stream.eatWhile(/[\d]/) && stream.eat(";");
- }
- } else {
- ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
- }
- return ok ? "atom" : "error";
- } else {
- stream.eatWhile(/[^&<]/);
- return null;
- }
- }
- inText.isInText = true;
-
- function inTag(stream, state) {
- var ch = stream.next();
- if (ch == ">" || (ch == "/" && stream.eat(">"))) {
- state.tokenize = inText;
- type = ch == ">" ? "endTag" : "selfcloseTag";
- return "tag bracket";
- } else if (ch == "=") {
- type = "equals";
- return null;
- } else if (ch == "<") {
- state.tokenize = inText;
- state.state = baseState;
- state.tagName = state.tagStart = null;
- var next = state.tokenize(stream, state);
- return next ? next + " tag error" : "tag error";
- } else if (/[\'\"]/.test(ch)) {
- state.tokenize = inAttribute(ch);
- state.stringStartCol = stream.column();
- return state.tokenize(stream, state);
- } else {
- stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
- return "word";
- }
- }
-
- function inAttribute(quote) {
- var closure = function(stream, state) {
- while (!stream.eol()) {
- if (stream.next() == quote) {
- state.tokenize = inTag;
- break;
- }
- }
- return "string";
- };
- closure.isInAttribute = true;
- return closure;
- }
-
- function inBlock(style, terminator) {
- return function(stream, state) {
- while (!stream.eol()) {
- if (stream.match(terminator)) {
- state.tokenize = inText;
- break;
- }
- stream.next();
- }
- return style;
- };
- }
- function doctype(depth) {
- return function(stream, state) {
- var ch;
- while ((ch = stream.next()) != null) {
- if (ch == "<") {
- state.tokenize = doctype(depth + 1);
- return state.tokenize(stream, state);
- } else if (ch == ">") {
- if (depth == 1) {
- state.tokenize = inText;
- break;
- } else {
- state.tokenize = doctype(depth - 1);
- return state.tokenize(stream, state);
- }
- }
- }
- return "meta";
- };
- }
-
- function Context(state, tagName, startOfLine) {
- this.prev = state.context;
- this.tagName = tagName;
- this.indent = state.indented;
- this.startOfLine = startOfLine;
- if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
- this.noIndent = true;
- }
- function popContext(state) {
- if (state.context) state.context = state.context.prev;
- }
- function maybePopContext(state, nextTagName) {
- var parentTagName;
- while (true) {
- if (!state.context) {
- return;
- }
- parentTagName = state.context.tagName;
- if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
- !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
- return;
- }
- popContext(state);
- }
- }
-
- function baseState(type, stream, state) {
- if (type == "openTag") {
- state.tagStart = stream.column();
- return tagNameState;
- } else if (type == "closeTag") {
- return closeTagNameState;
- } else {
- return baseState;
- }
- }
- function tagNameState(type, stream, state) {
- if (type == "word") {
- state.tagName = stream.current();
- setStyle = "tag";
- return attrState;
- } else {
- setStyle = "error";
- return tagNameState;
- }
- }
- function closeTagNameState(type, stream, state) {
- if (type == "word") {
- var tagName = stream.current();
- if (state.context && state.context.tagName != tagName &&
- config.implicitlyClosed.hasOwnProperty(state.context.tagName))
- popContext(state);
- if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
- setStyle = "tag";
- return closeState;
- } else {
- setStyle = "tag error";
- return closeStateErr;
- }
- } else {
- setStyle = "error";
- return closeStateErr;
- }
- }
-
- function closeState(type, _stream, state) {
- if (type != "endTag") {
- setStyle = "error";
- return closeState;
- }
- popContext(state);
- return baseState;
- }
- function closeStateErr(type, stream, state) {
- setStyle = "error";
- return closeState(type, stream, state);
- }
-
- function attrState(type, _stream, state) {
- if (type == "word") {
- setStyle = "attribute";
- return attrEqState;
- } else if (type == "endTag" || type == "selfcloseTag") {
- var tagName = state.tagName, tagStart = state.tagStart;
- state.tagName = state.tagStart = null;
- if (type == "selfcloseTag" ||
- config.autoSelfClosers.hasOwnProperty(tagName)) {
- maybePopContext(state, tagName);
- } else {
- maybePopContext(state, tagName);
- state.context = new Context(state, tagName, tagStart == state.indented);
- }
- return baseState;
- }
- setStyle = "error";
- return attrState;
- }
- function attrEqState(type, stream, state) {
- if (type == "equals") return attrValueState;
- if (!config.allowMissing) setStyle = "error";
- return attrState(type, stream, state);
- }
- function attrValueState(type, stream, state) {
- if (type == "string") return attrContinuedState;
- if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
- setStyle = "error";
- return attrState(type, stream, state);
- }
- function attrContinuedState(type, stream, state) {
- if (type == "string") return attrContinuedState;
- return attrState(type, stream, state);
- }
-
- return {
- startState: function(baseIndent) {
- var state = {tokenize: inText,
- state: baseState,
- indented: baseIndent || 0,
- tagName: null, tagStart: null,
- context: null}
- if (baseIndent != null) state.baseIndent = baseIndent
- return state
- },
-
- token: function(stream, state) {
- if (!state.tagName && stream.sol())
- state.indented = stream.indentation();
-
- if (stream.eatSpace()) return null;
- type = null;
- var style = state.tokenize(stream, state);
- if ((style || type) && style != "comment") {
- setStyle = null;
- state.state = state.state(type || style, stream, state);
- if (setStyle)
- style = setStyle == "error" ? style + " error" : setStyle;
- }
- return style;
- },
-
- indent: function(state, textAfter, fullLine) {
- var context = state.context;
- // Indent multi-line strings (e.g. css).
- if (state.tokenize.isInAttribute) {
- if (state.tagStart == state.indented)
- return state.stringStartCol + 1;
- else
- return state.indented + indentUnit;
- }
- if (context && context.noIndent) return CodeMirror.Pass;
- if (state.tokenize != inTag && state.tokenize != inText)
- return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
- // Indent the starts of attribute names.
- if (state.tagName) {
- if (config.multilineTagIndentPastTag !== false)
- return state.tagStart + state.tagName.length + 2;
- else
- return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
- }
- if (config.alignCDATA && /$/,
- blockCommentStart: "",
-
- configuration: config.htmlMode ? "html" : "xml",
- helperType: config.htmlMode ? "html" : "xml",
-
- skipAttribute: function(state) {
- if (state.state == attrValueState)
- state.state = attrState
- }
- };
-});
-
-CodeMirror.defineMIME("text/xml", "xml");
-CodeMirror.defineMIME("application/xml", "xml");
-if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
- CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
-
-});
diff --git a/js/DevHelper/full/template_edit.js b/js/DevHelper/full/template_edit.js
deleted file mode 100755
index 64f19a4..0000000
--- a/js/DevHelper/full/template_edit.js
+++ /dev/null
@@ -1,132 +0,0 @@
-/** @param {jQuery} $ jQuery Object */
-!function ($) {
-
- var defaultExtraKeys = {};
- defaultExtraKeys['Tab'] = 'indentMore';
- defaultExtraKeys['Shift-Tab'] = 'indentLess';
-
- var defaultConfig = {
- mode: 'htmlmixed',
- lineNumbers: 1,
- indentWithTabs: 1,
- smartIndent: 1,
- tabSize: 4,
- indentUnit: 4,
- extraKeys: defaultExtraKeys
- };
-
- if (XenForo.TemplateEditor) {
- var targetPrototype = XenForo.TemplateEditor.prototype;
-
- targetPrototype.DevHelper_extraKeysSave = function () {
- $('#saveReloadButton').trigger('click');
- };
-
- targetPrototype.DevHelper_setupCM = function (editor) {
- var $textarea = editor.$textarea;
- var $textareaWrapper = this.getTextareaWrapper();
-
- // default to use the mixed HTML mode
- var cmMode = 'htmlmixed';
- if (editor.$title.val().indexOf('.css') !== -1) {
- // we are editing a CSS template
- // switch to CSS mode
- cmMode = 'css';
- }
-
- var config = defaultConfig;
- config['value'] = $textarea.val();
- config['mode'] = cmMode;
-
- config.extraKeys['Cmd-S'] = $.context(this, 'DevHelper_extraKeysSave');
- config.extraKeys['Ctrl-S'] = $.context(this, 'DevHelper_extraKeysSave');
-
- var theCM = CodeMirror(function () {
- }, config);
- theCM.on('change', function (cm) {
- $textarea.val(cm.getValue());
- });
-
- var $wrapper = $(theCM.getWrapperElement());
- $wrapper.width($textareaWrapper.parent().width());
-
- // append the CodeMirror editor's wrapper to the page
- $textareaWrapper.append($wrapper);
- theCM.refresh();
-
- // hide the textarea
- $textarea.xfHide();
-
- // save it for later access
- editor.theCM = theCM;
- editor.$theCMWrapper = $wrapper;
- };
-
- var originalInitializePrimaryEditor = targetPrototype.initializePrimaryEditor;
- targetPrototype.initializePrimaryEditor = function () {
- originalInitializePrimaryEditor.call(this);
-
- var templateTitle = this.$titleOriginal.strval();
- var editor = this.editors[templateTitle];
-
- this.DevHelper_setupCM(editor);
- };
-
- var originalCreateEditor = targetPrototype.createEditor;
- targetPrototype.createEditor = function (templateTitle, $prevTab) {
- var editor = originalCreateEditor.call(this, templateTitle, $prevTab);
-
- this.DevHelper_setupCM(editor);
-
- // immediately hide the editor
- editor.$theCMWrapper.xfHide();
-
- return editor;
- };
-
- targetPrototype.switchEditor = function (e) {
- var $target = $(e.target).closest('a'),
- editor;
-
- // switch the active tab
- $target.closest('li')
- .addClass('active')
- .siblings().removeClass('active');
-
- // hide all CodeMirror instances
- $('.CodeMirror', this.getTextareaWrapper()).xfHide();
-
- editor = this.editors[$target.attr('templateTitle')];
-
- // display the only needed one
- editor.$theCMWrapper.xfShow();
-
- return false;
- };
- }
-
- XenForo.DevHelper_CodeMirror_TextArea = function ($textarea) {
- $textarea.each(function () {
- var config = defaultConfig;
-
- //config['viewportMargin'] = 'Infinity';
-
- var functionSave = function () {
- $textarea.parents('form').find('input.button.primary').trigger('click');
- };
- config.extraKeys['Cmd-S'] = functionSave;
- config.extraKeys['Ctrl-S'] = functionSave;
-
- var theCM = CodeMirror.fromTextArea(this, defaultConfig);
-
- theCM.on('change', function (cm) {
- $textarea.val(cm.getValue());
- });
- $(theCM.getWrapperElement()).addClass('DevHelper_CodeMirror_TextArea');
- });
- };
-
- // support template modification `find` textarea
- XenForo.register('textarea#ctrl_find.code', 'XenForo.DevHelper_CodeMirror_TextArea');
- XenForo.register('textarea#ctrl_replace.code', 'XenForo.DevHelper_CodeMirror_TextArea');
-}(jQuery);
\ No newline at end of file
diff --git a/library/DevHelper/Autoloader.php b/library/DevHelper/Autoloader.php
deleted file mode 100644
index fd5f4fb..0000000
--- a/library/DevHelper/Autoloader.php
+++ /dev/null
@@ -1,175 +0,0 @@
-_setup) {
- return;
- }
-
- $this->_rootDir = $rootDir;
- spl_autoload_register(array($this, 'autoload'));
-
- $this->_setup = true;
- }
-
- public function autoload($class)
- {
- if (class_exists($class, false) || interface_exists($class, false)) {
- return true;
- }
-
- if ($class == 'utf8_entity_decoder') {
- return false;
- }
-
- if (substr($class, 0, 5) == 'XFCP_') {
- return false;
- }
-
- $filename = $this->autoloaderClassToFile($class);
- if (!$filename) {
- return false;
- }
-
- if (file_exists($filename)) {
- /** @noinspection PhpIncludeInspection */
- require($filename);
- if (class_exists($class, false) || interface_exists($class, false)) {
- return true;
- }
- }
-
- return false;
- }
-
- public static function throwIfNotSetup()
- {
- if (empty($_SERVER['SCRIPT_FILENAME'])) {
- throw new XenForo_Exception('Cannot get value for $_SERVER[\'SCRIPT_FILENAME\']');
- }
-
- /** @var XenForo_Application $app */
- $app = XenForo_Application::getInstance();
- $root = $app->getRootDir();
- $candidates = array(
- file_get_contents($root . '/index.php'),
- file_get_contents($root . '/admin.php'),
- );
-
- if (empty($_SERVER['DEVHELPER_ROUTER_PHP'])
- && in_array(file_get_contents($_SERVER['SCRIPT_FILENAME']), $candidates, true)
- && !self::$_instance
- ) {
- throw new XenForo_Exception('DevHelper_Autoloader must be setup');
- }
-
- if (!class_exists('DevHelper_XenForo_Autoloader')) {
- eval('class DevHelper_XenForo_Autoloader extends XenForo_Autoloader {
- public static function setInstanceCarelessly($loader = null)
- {
- self::$_instance = $loader;
- }
- }');
- }
-
- /** @noinspection PhpUndefinedClassInspection */
- DevHelper_XenForo_Autoloader::setInstanceCarelessly(self::$_instance);
- }
-
- final public static function getDevHelperInstance()
- {
- if (!self::$_instance) {
- self::$_instance = new self();
- }
-
- return self::$_instance;
- }
-
- public function autoloaderClassToFile($class)
- {
- static $classes = array(
- 'XenForo_CodeEvent',
- 'XenForo_Debug',
- 'XenForo_Template_Abstract',
- 'XenForo_ViewRenderer_Json',
- );
- if (in_array($class, $classes, true)) {
- $class = 'DevHelper_' . $class;
- }
-
- $classFile = $this->_autoloaderClassToFile($class);
- $classFile = $this->_locateClassFile($classFile);
-
- $strPos = 0;
- if (substr($class, 0, 9) !== 'DevHelper') {
- $strPos = strpos($class, 'ShippableHelper_');
- }
- if ($strPos > 0) {
- // a helper class is being called, check its version vs. ours
- $classVersionId = 0;
- if (file_exists($classFile)) {
- $classContents = file_get_contents($classFile);
-
- $classVersionId = DevHelper_Helper_ShippableHelper::getVersionId($class, $classFile, $classContents);
- if ($classVersionId === false) {
- die('Add-on class version could not be detected: ' . $classFile);
- }
- }
-
- $oursClass = 'DevHelper_Helper_' . substr($class, $strPos);
- $oursFile = $this->_autoloaderClassToFile($oursClass);
- $oursFile = $this->_locateClassFile($oursFile);
- if (file_exists($oursFile)) {
- $oursContents = file_get_contents($oursFile);
-
- $oursVersionId = DevHelper_Helper_ShippableHelper::getVersionId($oursClass, $oursFile, $oursContents);
- if ($oursVersionId === false) {
- die('DevHelper class version could not be detected: ' . $oursFile);
- }
- } else {
- die('DevHelper file could not be found: ' . $oursFile);
- }
-
- if ($classVersionId < $oursVersionId) {
- if (!DevHelper_Helper_ShippableHelper::update($class, $classFile, $oursClass, $oursContents)) {
- die('Add-on file could not be updated: ' . $classFile);
- }
- }
- }
-
- return $classFile;
- }
-
- public function getRootDir()
- {
- return $this->_rootDir;
- }
-
- protected function _autoloaderClassToFile($class)
- {
- if (preg_match('#[^a-zA-Z0-9_\\\\]#', $class)) {
- return false;
- }
- return $this->_rootDir . '/' . str_replace(array('_', '\\'), '/', $class) . '.php';
- }
-
- protected function _locateClassFile($classFile)
- {
- if (!empty($_SERVER['DEVHELPER_ROUTER_PHP'])) {
- return DevHelper_Router::locateCached($classFile);
- }
-
- return $classFile;
- }
-}
diff --git a/library/DevHelper/Config/Base.php b/library/DevHelper/Config/Base.php
deleted file mode 100755
index 3470ea5..0000000
--- a/library/DevHelper/Config/Base.php
+++ /dev/null
@@ -1,415 +0,0 @@
-_upgrade();
-
- return empty($result);
- }
-
- public function getDataClasses()
- {
- return $this->_dataClasses;
- }
-
- public function getDataClass($name)
- {
- $name = $this->_normalizeDbName($name);
-
- if (!$this->checkDataClassExists($name)) {
- return array();
- }
-
- $dataClass = $this->_dataClasses[$name];
-
- foreach ($dataClass['files'] as &$file) {
- if (!empty($file)) {
- $path = DevHelper_Generator_File::getClassPath($file['className'], $this);
- $hash = DevHelper_Generator_File::calcHash($path);
- if ($hash != $file['hash']) {
- $file['changed'] = true;
- }
- }
- }
-
- return $dataClass;
- }
-
- public function addDataClass(
- $name,
- $fields = array(),
- $primaryKey = false,
- $indeces = array(),
- $extraData = array()
- ) {
- $name = $this->_normalizeDbName($name);
-
- $this->_dataClasses[$name] = array(
- 'name' => $name,
- 'camelCase' => DevHelper_Generator_File::getCamelCase($name),
- 'camelCasePlural' => false,
- 'camelCaseWSpace' => ucwords(str_replace('_', ' ', $name)),
- 'camelCasePluralWSpace' => false,
- 'fields' => array(),
- 'phrases' => array(),
- 'title_field' => false,
- 'primaryKey' => false,
- 'indeces' => array(),
-
- 'files' => array(
- 'data_writer' => false,
- 'model' => false,
- 'route_prefix_admin' => false,
- 'controller_admin' => false,
- ),
- );
-
- foreach ($extraData as $key => $value) {
- $this->_dataClasses[$name][$key] = $value;
- }
-
- foreach ($fields as $fieldName => $fieldInfo) {
- $fieldInfo = array_merge(array('name' => $fieldName), $fieldInfo);
- $this->addDataClassField($name, $fieldInfo);
- }
- $this->addDataClassPrimaryKey($name, $primaryKey);
- foreach ($indeces as $index) {
- $this->addDataClassIndex($name, $index);
- }
-
- return true;
- }
-
- public function addDataClassField($name, array $field)
- {
- $name = $this->_normalizeDbName($name);
- $field['name'] = $this->_normalizeDbName($field['name']);
- $field['type'] = strtolower($field['type']);
- if (!in_array($field['type'], DevHelper_Generator_Db::getDataTypes())) {
- $field['type'] = XenForo_DataWriter::TYPE_SERIALIZED;
- }
-
- if (empty($this->_dataClasses[$name]['title_field'])
- && in_array(
- $field['type'],
- array(XenForo_DataWriter::TYPE_STRING)
- )
- ) {
- $this->_dataClasses[$name]['title_field'] = $field['name'];
- }
-
- $this->_dataClasses[$name]['fields'][$field['name']] = $field;
-
- return true;
- }
-
- public function addDataClassPrimaryKey($name, $fields)
- {
- $name = $this->_normalizeDbName($name);
-
- if (!is_array($fields)) {
- $fields = array($fields);
- }
-
- $primaryKey = array();
-
- foreach ($fields as $field) {
- $field = $this->_normalizeDbName($field);
- if (!$this->checkDataClassFieldExists($name, $field)) {
- return false;
- }
- $primaryKey[] = $field;
- }
-
- if (!empty($primaryKey)) {
- $this->_dataClasses[$name]['primaryKey'] = $primaryKey;
- return true;
- }
-
- return false;
- }
-
- public function addDataClassIndex($name, array $index)
- {
- $name = $this->_normalizeDbName($name);
- $fields = array();
-
- if (!is_array($index['fields'])) {
- $index['fields'] = array($index['fields']);
- }
-
- foreach ($index['fields'] as $field) {
- $field = $this->_normalizeDbName($field);
- if ($this->checkDataClassFieldExists($name, $field)) {
- $fields[] = $field;
- } else {
- return false;
- }
- }
- if (empty($fields)) {
- return false;
- }
-
- if (isset($index['name'])) {
- $indexName = $index['name'];
- } else {
- $indexName = implode('_', $fields);
- }
-
- $type = !empty($index['type']) ? $index['type'] : 'NORMAL';
- if (!in_array(strtoupper($type), DevHelper_Generator_Db::getIndexTypes())) {
- $type = 'NORMAL';
- }
-
- $this->_dataClasses[$name]['indeces'][$indexName] = array(
- 'name' => $indexName,
- 'fields' => $fields,
- 'type' => strtoupper($type),
- );
-
- return true;
- }
-
- public function updateDataClassFile($name, $fileType, $className, $path)
- {
- $name = $this->_normalizeDbName($name);
-
- $this->_dataClasses[$name]['files'][$fileType] = array(
- 'className' => $className,
- 'hash' => DevHelper_Generator_File::calcHash($path),
- );
- }
-
- public function checkDataClassExists($name)
- {
- $name = $this->_normalizeDbName($name);
-
- return isset($this->_dataClasses[$name]);
- }
-
- public function checkDataClassFieldExists($name, $field)
- {
- $name = $this->_normalizeDbName($name);
- $field = $this->_normalizeDbName($field);
-
- return isset($this->_dataClasses[$name]['fields'][$field]);
- }
-
- public function getDataPatches()
- {
- return $this->_dataPatches;
- }
-
- public function addDataPatch($table, array $patch)
- {
- if (!empty($patch['index'])) {
- if (!isset($patch['name'])) {
- $patch['name'] = implode('_', $patch['fields']);
- }
- $patchKey = 'index::' . $patch['name'];
-
- if (isset($patch['type'])) {
- $patch['type'] = strtoupper($patch['type']);
- } else {
- $patch['type'] = '';
- }
- if (!in_array($patch['type'], DevHelper_Generator_Db::getIndexTypes())) {
- $patch['type'] = 'NORMAL';
- }
-
- if (!isset($patch['fields'])) {
- throw new XenForo_Exception('addDataPatch(index=true) requires `fields`');
- }
- if (!is_array($patch['fields'])) {
- $patch['fields'] = array(strval($patch['fields']));
- }
- } else {
- $patch['name'] = DevHelper_Generator_Db::getFieldName($this, $this->_normalizeDbName($patch['name']));
- $patchKey = $patch['name'];
-
- if (!isset($patch['type'])) {
- throw new XenForo_Exception('addDataPatch() requires `type`');
- }
- $patch['type'] = strtolower($patch['type']);
- if (!in_array($patch['type'], DevHelper_Generator_Db::getDataTypes())) {
- $patch['type'] = XenForo_DataWriter::TYPE_SERIALIZED;
- }
- }
-
- $this->_dataPatches[$table][$patchKey] = $patch;
-
- return true;
- }
-
- public function setExportPath($path)
- {
- if (is_dir($path) AND is_writable($path)) {
- $this->_exportPath = $path;
- } else {
- die('EXPORT PATH IS NOT WRITABLE');
- }
- }
-
- public function getExportPath()
- {
- $path = $this->_exportPath;
-
- if (!is_string($path)) {
- $path = '';
- }
-
- if (isset($_SERVER['DEVHELPER_ROUTER_PHP'])) {
- $path = sprintf(
- '%s/xenforo/data/exported/%s',
- dirname($_SERVER['DEVHELPER_ROUTER_PHP']),
- str_replace('_', DIRECTORY_SEPARATOR, $this->getClassPrefix())
- );
- XenForo_Helper_File::createDirectory($path);
- }
-
- if ($path === '') {
- // fallback
- $path = DevHelper_Generator_File::getAddOnIdPath($this);
- }
-
- if (!is_dir($path) || !is_writable($path)) {
- die(sprintf('Export path (%s) is not writable', $path));
- }
-
- return $path;
- }
-
- public function getExportIncludes()
- {
- return $this->_exportIncludes;
- }
-
- public function getExportExcludes()
- {
- return $this->_exportExcludes;
- }
-
- public function getExportAddOns()
- {
- return $this->_exportAddOns;
- }
-
- public function getExportStyles()
- {
- return $this->_exportStyles;
- }
-
- public function getPrefix()
- {
- if (!empty($this->_options['prefix'])) {
- return $this->_options['prefix'];
- }
-
- $configClassName = get_class($this);
- $parts = explode('_', $configClassName);
- array_pop($parts);
- array_pop($parts);
- $prefix = implode('_', $parts);
-
- return $prefix;
- }
-
- public function getClassPrefix()
- {
- if (!empty($this->_options['classPrefix'])) {
- return $this->_options['classPrefix'];
- }
-
- return $this->getPrefix();
- }
-
- public function outputSelf()
- {
- $className = get_class($this);
-
- $dataClasses = DevHelper_Generator_File::varExport($this->_dataClasses);
- $dataPatches = DevHelper_Generator_File::varExport($this->_dataPatches);
- $exportPath = DevHelper_Generator_File::varExport($this->_exportPath);
- $exportIncludes = DevHelper_Generator_File::varExport($this->_exportIncludes);
- $exportExcludes = DevHelper_Generator_File::varExport($this->_exportExcludes);
- $exportAddOns = DevHelper_Generator_File::varExport($this->_exportAddOns);
- $exportStyles = DevHelper_Generator_File::varExport($this->_exportStyles);
- $options = DevHelper_Generator_File::varExport($this->_options);
-
- $contents = <<addDataClass(
- 'name_here',
- array( // fields
- 'field_here' => array(
- 'type' => 'type_here',
- // 'length' => 'length_here',
- // 'required' => true,
- // 'allowedValues' => array('value_1', 'value_2'),
- // 'default' => 0,
- // 'autoIncrement' => true,
- ),
- // other fields go here
- ),
- array('primary_key_1', 'primary_key_2'), // or 'primary_key', both are okie
- array( // indeces
- array(
- 'fields' => array('field_1', 'field_2'),
- 'type' => 'NORMAL', // UNIQUE or FULLTEXT
- ),
- ),
- );
- */
- }
-}
-EOF;
-
- return $contents;
- }
-
- protected function _normalizeDbName($name)
- {
- return $this->_normalizeName($name);
- }
-
- protected function _normalizeName($name)
- {
- return preg_replace('/[^a-zA-Z_]/', '', $name);
- }
-}
diff --git a/library/DevHelper/ControllerHelper/AddOn.php b/library/DevHelper/ControllerHelper/AddOn.php
deleted file mode 100644
index 894ff69..0000000
--- a/library/DevHelper/ControllerHelper/AddOn.php
+++ /dev/null
@@ -1,65 +0,0 @@
-Sync Now.',
- XenForo_Link::buildAdminLink('tools/dev/helper/sync')
- ));
- }
- }
-
- public function filterKeepActiveAddOns(array &$dataGrouped, array $addOns = null)
- {
- $removedCount = 0;
-
- if ($addOns === null) {
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = $this->_controller->getModelFromCache('XenForo_Model_AddOn');
- $addOns = $addOnModel->getAllAddOns();
- }
-
- foreach ($addOns as $addOn) {
- if (empty($addOn['active'])) {
- // remove template modifications from inactive add-ons
- if (!empty($dataGrouped[$addOn['addon_id']])) {
- $removedCount += count($dataGrouped[$addOn['addon_id']]);
- unset($dataGrouped[$addOn['addon_id']]);
- }
- }
- }
-
- return $removedCount;
- }
-
- public function filterKeepActiveAddOnsDirect(array &$data, array $addOns = null)
- {
- $removedCount = 0;
-
- if ($addOns === null) {
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = $this->_controller->getModelFromCache('XenForo_Model_AddOn');
- $addOns = $addOnModel->getAllAddOns();
- }
-
- foreach (array_keys($data) as $dataId) {
- $singleRef = &$data[$dataId];
-
- if (empty($addOns[$singleRef['addon_id']])) {
- continue;
- }
- $addOnRef = &$addOns[$singleRef['addon_id']];
-
- if (empty($addOnRef['active'])) {
- $removedCount++;
- unset($data[$dataId]);
- }
- }
-
- return $removedCount;
- }
-}
diff --git a/library/DevHelper/Generator/Code/Common.php b/library/DevHelper/Generator/Code/Common.php
deleted file mode 100755
index b4d5db9..0000000
--- a/library/DevHelper/Generator/Code/Common.php
+++ /dev/null
@@ -1,233 +0,0 @@
-_className === false) {
- throw new Exception('_setClassName() must be called before calling _generate()');
- }
-
- $output = '';
-
- $output .= "_className}";
- if ($this->_baseClass !== false) {
- $output .= " extends {$this->_baseClass}";
- }
- if (!empty($this->_interfaces)) {
- $isFirstInterface = true;
- $output .= " implements";
- foreach ($this->_interfaces as $interface) {
- if ($isFirstInterface) {
- $isFirstInterface = false;
- } else {
- $output .= ",";
- }
- $output .= " {$interface}";
- }
- }
- $output .= "\n{\n";
-
- $output .= "\n " . DevHelper_Generator_File::COMMENT_AUTO_GENERATED_START . "\n";
-
- if (!empty($this->_constants)) {
- $output .= "\n";
- foreach ($this->_constants as $constantName => $constantValue) {
- $output .= " const {$constantName} = {$constantValue};\n";
- }
- }
-
- if (!empty($this->_properties)) {
- $output .= "\n";
- foreach ($this->_properties as $propertyName => $propertyDeclare) {
- if ($propertyDeclare != null) {
- $output .= " {$propertyDeclare};\n";
- } else {
- $output .= " protected {$propertyName};\n";
- }
- }
- }
-
- if (!empty($this->_methods)) {
- foreach ($this->_methods as $method) {
- $output .= "\n";
- $output .= $this->_generateMethod($method);
- }
- }
-
- $output .= "\n " . DevHelper_Generator_File::COMMENT_AUTO_GENERATED_END . "\n";
-
- if (!empty($this->_customizableMethods)) {
- foreach ($this->_customizableMethods as $method) {
- $output .= "\n";
- $output .= $this->_generateMethod($method);
- }
- }
-
- $output .= "\n";
-
- // class ClassName {
- $output .= "}\n";
-
- return $output;
- }
-
- protected function _generateMethod($method, $level = 1)
- {
- $output = "";
- $indentation = str_repeat(" ", $level);
-
- $output .= $indentation;
- if ($method['visibility'] != '') {
- $output .= "{$method['visibility']} ";
- }
- $output .= "function {$method['name']}(";
-
- $isFirstParam = true;
- foreach ($method['params'] as $paramName => $paramDeclare) {
- if ($isFirstParam) {
- $isFirstParam = false;
- } else {
- $output .= ", ";
- }
-
- if ($paramDeclare != null) {
- $output .= "{$paramDeclare}";
- } else {
- $output .= "{$paramName}";
- }
- }
-
- $output .= ")\n {\n";
-
- $codeBlocks = $method['code'];
- ksort($codeBlocks);
- $isFirstCodeBlock = true;
- foreach ($codeBlocks as $codeBlock) {
- $lines = explode("\n", $codeBlock);
- $codeBlockOutput = '';
- $foundNonEmptyLine = false;
-
- foreach ($lines as $line) {
- $trimmed = trim($line);
-
- if (strlen($trimmed) == 0) {
- // this is an empty line, only output if we have found some non-empty line before
- if ($foundNonEmptyLine) {
- $codeBlockOutput .= "\n";
- }
- } else {
- $foundNonEmptyLine = true;
- $codeBlockOutput .= "{$indentation} {$line}\n";
- }
- }
-
- // remove the last empty lines
- $codeBlockOutput = rtrim($codeBlockOutput);
-
- if ($isFirstCodeBlock) {
- $isFirstCodeBlock = false;
- } else {
- $output .= "\n";
- }
- $output .= $codeBlockOutput . "\n";
- }
-
- $output .= "{$indentation}}\n";
-
- return $output;
- }
-
- protected function _setClassName($className)
- {
- $this->_className = $className;
- }
-
- protected function _setBaseClass($className)
- {
- $this->_baseClass = $className;
- }
-
- protected function _addInterface($interface)
- {
- $this->_interfaces[] = $interface;
- }
-
- protected function _addConstant($name, $value)
- {
- $this->_constants[$name] = $value;
- }
-
- protected function _addProperty($name, $declare = null)
- {
- $this->_properties[$name] = $declare;
- }
-
- protected function _addMethod($name, $visibility, array $params, $code, $codeId = null)
- {
- $this->_addMethodCommon($this->_methods, $name, $visibility, $params);
-
- // switch code with existing code (detected by codeId) if needed
- if (!empty($code)) {
- if ($codeId !== null) {
- $this->_methods[$name]['code'][$codeId] = $code;
- } else {
- $this->_methods[$name]['code'][] = $code;
- }
- }
- }
-
- protected function _addCustomizableMethod($name, $visibility, array $params)
- {
- $this->_addMethodCommon($this->_customizableMethods, $name, $visibility, $params);
-
- $this->_customizableMethods[$name]['code'] = array('// customized code goes here');
- }
-
- protected function _addMethodCommon(array &$methods, $name, $visibility, $params)
- {
- if (!isset($methods[$name])) {
- $methods[$name] = array(
- 'name' => $name,
- 'visibility' => '',
- 'params' => array(),
- 'code' => array(),
- );
- }
-
- // we have to use the broader visibility between
- static $visibilities = array(
- '',
- 'private',
- 'protected',
- 'public',
- 'protected static',
- 'public static'
- );
- $oldVisibilityLevel = array_search($methods[$name]['visibility'], $visibilities);
- $newVisibilityLevel = array_search($visibility, $visibilities);
- $max = max(array(
- $oldVisibilityLevel,
- $newVisibilityLevel
- ));
- $methods[$name]['visibility'] = $visibilities[$max];
-
- foreach ($params as $paramName => $paramDeclare) {
- if (is_numeric($paramName)) {
- $paramName = $paramDeclare;
- $paramDeclare = null;
- }
- $methods[$name]['params'][$paramName] = $paramDeclare;
- }
- }
-}
diff --git a/library/DevHelper/Generator/Code/ControllerAdmin.php b/library/DevHelper/Generator/Code/ControllerAdmin.php
deleted file mode 100755
index 9d13a9d..0000000
--- a/library/DevHelper/Generator/Code/ControllerAdmin.php
+++ /dev/null
@@ -1,724 +0,0 @@
-_addOn = $addOn;
- $this->_config = $config;
- $this->_dataClass = $dataClass;
- $this->_info = $info;
- }
-
- protected function _generate()
- {
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate %s: too many fields in primary key',
- $this->_info['controller']
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- $variableName = DevHelper_Generator_Code_Model::getVariableName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $variableNamePlural = DevHelper_Generator_Code_Model::getVariableNamePlural(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $modelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $modelGetFunctionName = DevHelper_Generator_Code_Model::generateGetDataFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $dataWriterClassName = DevHelper_Generator_Code_DataWriter::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $viewListClassName = $this->_getViewClassName('list');
- $viewEditClassName = $this->_getViewClassName('edit');
- $viewDeleteClassName = $this->_getViewClassName('delete');
-
- $imageField = DevHelper_Generator_Db::getImageField($this->_dataClass['fields']);
- list($templateList, $templateEdit, $templateDelete) = $this->_generateTemplates(
- $variableName,
- $variableNamePlural,
- $imageField
- );
-
- $this->_setClassName($this->_info['controller']);
- $this->_setBaseClass('XenForo_ControllerAdmin_Abstract');
-
- $actionIndexConditions = array();
- $actionIndexFetchOptions = array();
-
- if (isset($this->_dataClass['fields']['display_order'])) {
- $actionIndexFetchOptions['order'] = 'display_order';
- }
-
- $actionIndexConditions = DevHelper_Generator_File::varExport($actionIndexConditions);
- $actionIndexFetchOptions = DevHelper_Generator_File::varExport($actionIndexFetchOptions);
-
- $this->_addMethod('actionIndex', 'public', array(), "
-
-\$conditions = {$actionIndexConditions};
-\$fetchOptions = {$actionIndexFetchOptions};
-
-\${$variableName}Model = \$this->_get{$this->_dataClass['camelCase']}Model();
-\${$variableNamePlural} = \${$variableName}Model->{$modelGetFunctionName}(\$conditions, \$fetchOptions);
-
-\$viewParams = array(
- '{$variableNamePlural}' => \${$variableNamePlural},
-);
-
-return \$this->responseView('$viewListClassName', '$templateList', \$viewParams);
- ");
-
- $this->_addMethod('actionAdd', 'public', array(), "
-
-\$viewParams = array(
- '$variableName' => array(),
-);
-
- ", '000');
-
- $this->_addMethod('actionAdd', 'public', array(), "
-
-return \$this->responseView('$viewEditClassName', '$templateEdit', \$viewParams);
-
- ", '999');
-
- $this->_addMethod('actionEdit', 'public', array(), "
-
-\$id = \$this->_input->filterSingle('{$idField}', XenForo_Input::UINT);
-\${$variableName} = \$this->_get{$this->_dataClass['camelCase']}OrError(\$id);
-
-\$viewParams = array(
- '$variableName' => \${$variableName},
-);
-
- ", '000');
-
- $this->_addMethod('actionEdit', 'public', array(), "
-
-return \$this->responseView('$viewEditClassName', '$templateEdit', \$viewParams);
-
- ", '999');
-
- $this->_addMethod('actionSave', 'public', array(), "
-
-\$this->_assertPostOnly();
-
-\$id = \$this->_input->filterSingle('{$idField}', XenForo_Input::UINT);
-\$dw = \$this->_get{$this->_dataClass['camelCase']}DataWriter();
-if (\$id) {
- \$dw->setExistingData(\$id);
-}
-
- ", '000');
-
- $this->_addMethod('actionSave', 'public', array(), "
-
-\$this->_prepareDwBeforeSaving(\$dw);
-
-\$dw->save();
-
-return \$this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('{$this->_info['routePrefix']}')
-);
-
- ", '999');
-
- $this->_addMethod('actionDelete', 'public', array(), "
-
-\$id = \$this->_input->filterSingle('{$idField}', XenForo_Input::UINT);
-\${$variableName} = \$this->_get{$this->_dataClass['camelCase']}OrError(\$id);
-
-if (\$this->isConfirmedPost()) {
- \$dw = \$this->_get{$this->_dataClass['camelCase']}DataWriter();
- \$dw->setExistingData(\$id);
- \$dw->delete();
-
- return \$this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('{$this->_info['routePrefix']}')
- );
-} else {
- \$viewParams = array(
- '$variableName' => \${$variableName},
- );
-
- return \$this->responseView('$viewDeleteClassName', '$templateDelete', \$viewParams);
-}
-
- ");
-
- $phraseNotFound = $this->_getPhraseName('_not_found');
- $this->_addMethod("_get{$this->_dataClass['camelCase']}OrError", 'protected', array(
- '$id',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\${$variableName} = \$this->_get{$this->_dataClass['camelCase']}Model()->get{$this->_dataClass['camelCase']}ById(\$id, \$fetchOptions);
-
-if (empty(\${$variableName})) {
- throw \$this->responseException(\$this->responseError(new XenForo_Phrase('$phraseNotFound'), 404));
-}
-
-return \${$variableName};
-
- ");
-
- $this->_addMethod("_get{$this->_dataClass['camelCase']}Model", 'protected', array(), "
-
-return \$this->getModelFromCache('$modelClassName');
-
- ");
-
- $this->_addMethod(" _get{$this->_dataClass['camelCase']}DataWriter", 'protected', array(), "
-
-return XenForo_DataWriter::create('$dataWriterClassName');
-
- ");
-
- $this->_addCustomizableMethod(
- '_prepareDwBeforeSaving',
- 'protected',
- array('$dw' => "$dataWriterClassName \$dw")
- );
-
- return parent::_generate();
- }
-
- protected function _generateTemplates($variableName, $variableNamePlural, $imageField)
- {
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate %s: too many fields in primary key',
- $this->_info['controller']
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- $dataTitle = "\${$variableName}"
- . (
- empty($this->_dataClass['title_field'])
- ? (".{$idField}")
- : (
- is_array($this->_dataClass['title_field'])
- ? (".{$this->_dataClass['title_field'][0]}.{$this->_dataClass['title_field'][1]}")
- : (".{$this->_dataClass['title_field']}")
- ));
-
- // create the phrases
- $phraseClassName = $this->_getPhraseName('');
- $phrasePlural = $this->_getPhrasePluralName();
- $phraseAdd = $this->_getPhraseName('_add');
- $phraseEdit = $this->_getPhraseName('_edit');
- $phraseDelete = $this->_getPhraseName('_delete');
- $phraseSave = $this->_getPhraseName('_save');
- $phraseConfirmDeletion = $this->_getPhraseName('_confirm_deletion');
- $phrasePleaseConfirm = $this->_getPhraseName('_please_confirm');
- $phraseNotFound = $this->_getPhraseName('_not_found');
- $phraseNoResults = $this->_getPhraseName('_no_results');
-
- $this->_generatePhrase($phraseClassName, $this->_dataClass['camelCaseWSpace']);
- if (!empty($this->_dataClass['camelCasePluralWSpace'])) {
- $this->_generatePhrase($phrasePlural, $this->_dataClass['camelCasePluralWSpace']);
- } else {
- $this->_generatePhrase($phrasePlural, $this->_dataClass['camelCaseWSpace'] . ' (Plural)');
- }
- $this->_generatePhrase($phraseAdd, 'Add New ' . $this->_dataClass['camelCaseWSpace']);
- $this->_generatePhrase($phraseEdit, 'Edit ' . $this->_dataClass['camelCaseWSpace']);
- $this->_generatePhrase($phraseDelete, 'Delete ' . $this->_dataClass['camelCaseWSpace']);
- $this->_generatePhrase($phraseSave, 'Save ' . $this->_dataClass['camelCaseWSpace']);
- $this->_generatePhrase($phraseConfirmDeletion, 'Confirm Deletion of ' . $this->_dataClass['camelCaseWSpace']);
- $this->_generatePhrase(
- $phrasePleaseConfirm,
- 'Please confirm that you want to delete the following ' . strtolower($this->_dataClass['camelCaseWSpace'])
- );
- $this->_generatePhrase(
- $phraseNotFound,
- 'The requested ' . strtolower($this->_dataClass['camelCaseWSpace']) . ' could not be found'
- );
- $this->_generatePhrase(
- $phraseNoResults,
- 'No ' . strtolower($this->_dataClass['camelCaseWSpace']) . ' could be found...'
- );
- // finished creating pharses
-
- $templateList = $this->_getTemplateTitle('_list');
- $templateListItems = $this->_getTemplateTitle('_list_items');
- $templateEdit = $this->_getTemplateTitle('_edit');
- $templateDelete = $this->_getTemplateTitle('_delete');
-
- // create the templates
- $listItemExtras = '';
- if ($imageField !== false) {
- $listItemExtras .= <<
- {xen:if '{\${$variableName}.images.s}', ' '}
-
-EOF;
- }
-
- $templateListItemsTemplate = <<
-
- {{$dataTitle}} {$listItemExtras}
-
-
-EOF;
- $this->_generateAdminTemplate($templateListItems, $templateListItemsTemplate);
- // finished template_list_items
-
- $templateListTemplate = <<{xen:phrase $phrasePlural}
-
-
- + {xen:phrase $phraseAdd}
-
-
-
-
-
-
-
-
-
- {xen:phrase $phrasePlural}
-
-
-
-
-
-
-
-
- {xen:phrase $phraseNoResults}
-
-
-EOF;
- $this->_generateAdminTemplate($templateList, $templateListTemplate);
- // finished template_list
-
- $templateEditFormExtra = 'class="AutoValidator" data-redirect="yes"';
- $templateEditFields = '';
- $extraViewParamsForTemplateEdit = array();
- $filterParams = array();
-
- if (!empty($this->_dataClass['phrases'])) {
- foreach ($this->_dataClass['phrases'] as $phraseType) {
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $this->_dataClass['name'] . '_' . $phraseType
- );
-
- $templateEditFields .= <<
-EOF;
- }
- }
-
- foreach ($this->_dataClass['fields'] as $field) {
- if ($field['name'] == $idField) {
- continue;
- }
- if (empty($field['required'])) {
- // ignore non-required fields
- continue;
- }
- if ($field['name'] == $imageField) {
- // ignore image field
- continue;
- }
-
- // queue this field for validation
- $filterParams[$field['name']] = $field['type'];
-
- if ($field['name'] == $this->_dataClass['title_field']) {
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $field['name']
- );
- $templateEditFields .= <<
-EOF;
- continue;
- }
-
- if (substr($field['name'], -3) == '_id') {
- // link to another dataClass?
- $other = substr($field['name'], 0, -3);
- if ($this->_config->checkDataClassExists($other)) {
- // yeah!
- $otherDataClass = $this->_config->getDataClass($other);
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $otherDataClass,
- $otherDataClass['name']
- );
- $templateEditFields .= <<
-
-
-
-EOF;
- $otherDataClassModelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $otherDataClass
- );
- $extraViewParamsForTemplateEdit["list{$otherDataClass['camelCase']}"] =
- "\$viewParams['list{$otherDataClass['camelCase']}'] = " .
- "\$this->getModelFromCache('{$otherDataClassModelClassName}')->getList();";
- continue;
- } elseif ($other === $this->_dataClass['name'] . '_parent') {
- // just a parent of this same class
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $other
- );
- $templateEditFields .= <<
-
-
-
-EOF;
- $thisDataClassModelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $extraViewParamsForTemplateEdit["list{$this->_dataClass['camelCase']}"] =
- "\$viewParams['list{$this->_dataClass['camelCase']}'] = " .
- "\$this->getModelFromCache('{$thisDataClassModelClassName}')->getList();";
- continue;
- }
- }
-
- if ($field['name'] == 'display_order') {
- // special case with display_order
- $templateEditFields .= <<
-EOF;
- continue;
- }
-
- if ($field['type'] == 'string' && !empty($field['allowedValues'])) {
- // render field with enum type as a list of selections
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $field['name']
- );
-
- $templateEditFields .= <<
-
-EOF;
-
- foreach ($field['allowedValues'] as $allowedValue) {
- $allowedValuePhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $field['name'] . '_' . $allowedValue
- );
-
- $templateEditFields .= <<{xen:phrase $allowedValuePhraseName}
-EOF;
- }
-
- $templateEditFields .= <<
-EOF;
-
- continue;
- }
-
- if ($field['type'] == 'uint' AND substr($field['name'], 0, 3) === 'is_') {
- // special case with boolean fields
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $field['name']
- );
-
- $templateEditFields .= <<
-
-
-EOF;
-
- continue;
- }
-
- $fieldPhraseName = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $field['name']
- );
- $extra = '';
- if ($field['type'] == XenForo_DataWriter::TYPE_STRING AND (empty($field['length']) OR $field['length'] > 255)) {
- $extra .= ' rows="5"';
- }
- $templateEditFields .= <<
-EOF;
- }
-
- if ($imageField !== false) {
- $fieldPhraseImage = DevHelper_Generator_Phrase::generatePhraseAutoCamelCaseStyle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- 'image'
- );
- $templateEditFormExtra = 'enctype="multipart/form-data"';
-
- $templateEditFields .= <<
-
-
-
-
-
-
-
-
-EOF;
- }
-
- $templateEditTemplate = <<{xen:if '{\${$variableName}.{$idField}}', '{xen:phrase $phraseEdit}', '{xen:phrase $phraseAdd}'}
-
-
-
- $templateEditFields
-
-
-
-
-
-
-
-EOF;
-
- $this->_generateAdminTemplate($templateEdit, $templateEditTemplate);
-
- // add extra code for add, edit actions
- if (!empty($extraViewParamsForTemplateEdit)) {
- $this->_addMethod('actionAdd', 'public', array(), implode("\n", $extraViewParamsForTemplateEdit), '100');
- $this->_addMethod('actionEdit', 'public', array(), implode("\n", $extraViewParamsForTemplateEdit), '100');
- }
-
- // add input fields to action save
- if (!empty($this->_dataClass['phrases'])) {
- $phraseCode = "\$phrases = \$this->_input->filterSingle('_phrases', XenForo_Input::ARRAY_SIMPLE);\n";
- foreach ($this->_dataClass['phrases'] as $phraseType) {
- $dataWriterClassName = DevHelper_Generator_Code_DataWriter::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $dataPhraseConstantName = DevHelper_Generator_Code_DataWriter::generateDataPhraseConstant(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $phraseType
- );
-
- $phraseCode .= "if (isset(\$phrases['{$phraseType}']))\n{\n";
- $phraseCode .= " \$dw->setExtraData({$dataWriterClassName}::{$dataPhraseConstantName}, \$phrases['{$phraseType}']);\n";
- $phraseCode .= "}\n";
- }
-
- $this->_addMethod('actionSave', 'public', array(), "
-
-// get phrases from input data
-{$phraseCode}
-
- ", '100');
- }
-
- if (!empty($filterParams)) {
- $filterParamsExported = DevHelper_Generator_File::varExport($filterParams, 1);
- $this->_addMethod('actionSave', 'public', array(), "
-
-// get regular fields from input data
-\$dwInput = \$this->_input->filter($filterParamsExported);
-\$dw->bulkSet(\$dwInput);
-
- ", '200');
- }
-
- if ($imageField !== false) {
- // add image to action save
- $this->_addMethod('actionSave', 'public', array(), "
-
-// try to save the uploaded image if any
-\$image = XenForo_Upload::getUploadedFile('image');
-if (!empty(\$image)) {
- \$dw->setImage(\$image);
-}
-
- ", '300');
- }
- // finished template_edit
-
- $templateDeleteTemplate = <<{xen:phrase $phraseConfirmDeletion}: {{$dataTitle}}
-{xen:phrase $phraseConfirmDeletion}
-
-
- {{$dataTitle}}
-
-
-
-
-
-
- {xen:phrase $phrasePleaseConfirm}:
- {{$dataTitle}}
-
-
-
-
-
-EOF;
- $this->_generateAdminTemplate($templateDelete, $templateDeleteTemplate);
-
- // finished creating our templates
-
- return array(
- $templateList,
- $templateEdit,
- $templateDelete,
- );
- }
-
- protected function _getPhraseName($suffix)
- {
- return DevHelper_Generator_Phrase::getPhraseName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $this->_dataClass['name'] . $suffix
- );
- }
-
- protected function _getPhrasePluralName()
- {
- if (!empty($this->_dataClass['camelCasePluralWSpace'])) {
- $phrase = strtolower(str_replace(' ', '_', $this->_dataClass['camelCasePluralWSpace']));
- } else {
- $phrase = $this->_dataClass['name'] . '_plural';
- }
-
- return DevHelper_Generator_Phrase::getPhraseName($this->_addOn, $this->_config, $this->_dataClass, $phrase);
- }
-
- protected function _generatePhrase($phraseName, $phraseText)
- {
- DevHelper_Generator_Phrase::generatePhrase($this->_addOn, $phraseName, $phraseText);
- }
-
- protected function _getTemplateTitle($suffix)
- {
- return DevHelper_Generator_Template::getTemplateTitle(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $this->_dataClass['name'] . $suffix
- );
- }
-
- protected function _generateAdminTemplate($templateTitle, $templateHtml)
- {
- DevHelper_Generator_Template::generateAdminTemplate($this->_addOn, $templateTitle, $templateHtml);
- }
-
- protected function _getClassName()
- {
- return self::getClassName($this->_addOn, $this->_config, $this->_dataClass);
- }
-
- protected function _getViewClassName($view)
- {
- $subClassName = 'ViewAdmin_' . $this->_dataClass['camelCase'] . '_' . ucwords($view);
-
- return DevHelper_Generator_File::getClassName($this->_addOn['addon_id'], $subClassName, $this->_config);
- }
-
- public static function generate(array $addOn, DevHelper_Config_Base $config, array $dataClass, array $info)
- {
- $g = new self($addOn, $config, $dataClass, $info);
-
- return array(
- $g->_getClassName(),
- $g->_generate()
- );
- }
-
- public static function getClassName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return DevHelper_Generator_File::getClassName(
- $addOn['addon_id'],
- 'ControllerAdmin_' . $dataClass['camelCase'],
- $config
- );
- }
-}
diff --git a/library/DevHelper/Generator/Code/DataWriter.php b/library/DevHelper/Generator/Code/DataWriter.php
deleted file mode 100755
index 40ea5fc..0000000
--- a/library/DevHelper/Generator/Code/DataWriter.php
+++ /dev/null
@@ -1,395 +0,0 @@
-_addOn = $addOn;
- $this->_config = $config;
- $this->_dataClass = $dataClass;
- }
-
- protected function _generate()
- {
- $className = $this->_getClassName();
- $tableName = DevHelper_Generator_Db::getTableName($this->_config, $this->_dataClass['name']);
- $tableFields = $this->_dataClass['fields'];
- foreach ($tableFields as &$field) {
- unset($field['name']);
- if (!empty($field['length'])) {
- $field['maxLength'] = $field['length'];
- unset($field['length']);
- }
-
- $field['type'] = DevHelper_Generator_File::varExportConstantFromArray($field['type'], array(
- 'XenForo_DataWriter::TYPE_BOOLEAN',
- 'XenForo_DataWriter::TYPE_STRING',
- 'XenForo_DataWriter::TYPE_BINARY',
- 'XenForo_DataWriter::TYPE_INT',
- 'XenForo_DataWriter::TYPE_UINT',
- 'XenForo_DataWriter::TYPE_UINT_FORCED',
- 'XenForo_DataWriter::TYPE_FLOAT',
- 'XenForo_DataWriter::TYPE_SERIALIZED',
- 'XenForo_DataWriter::TYPE_JSON',
- 'XenForo_DataWriter::TYPE_UNKNOWN',
- ));
- }
- $tableFields = DevHelper_Generator_File::varExport($tableFields, 1);
- $primaryKey = DevHelper_Generator_File::varExport($this->_dataClass['primaryKey']);
- $modelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $this->_setClassName($className);
- $this->_setBaseClass('XenForo_DataWriter');
-
- $this->_addMethod('_getFields', 'protected', array(), "
-
-return array(
- '{$tableName}' => {$tableFields}
-);
-
- ");
-
- if (count($this->_dataClass['primaryKey']) == 1) {
- $idField = reset($this->_dataClass['primaryKey']);
- $this->_addMethod('_getExistingData', 'protected', array('$data'), "
-
-if (!\$id = \$this->_getExistingPrimaryKey(\$data, '{$idField}')) {
- return false;
-}
-
-return array('$tableName' => \$this->_get{$this->_dataClass['camelCase']}Model()->get{$this->_dataClass['camelCase']}ById(\$id));
-
- ");
- } else {
- $this->_addMethod('_getExistingData', 'protected', array('$data'), "
-
-throw new XenForo_Exception('{$className} does not support _getExistingData()');
-
- ");
- }
-
- $this->_addMethod('_getUpdateCondition', 'protected', array('$tableName'), "
-
-\$conditions = array();
-
-foreach ($primaryKey as \$field) {
- \$conditions[] = \$field . ' = ' . \$this->_db->quote(\$this->getExisting(\$field));
-}
-
-return implode(' AND ', \$conditions);
-
- ");
-
- $this->_addMethod("_get{$this->_dataClass['camelCase']}Model", 'protected', array(), "
-
-return \$this->getModelFromCache('$modelClassName');
-
- ");
-
- $this->_generateImageCode();
- $this->_generatePhrasesCode();
- $this->_generateParentCode();
-
- return parent::_generate();
- }
-
- protected function _generateImageCode()
- {
- $imageField = DevHelper_Generator_Db::getImageField($this->_dataClass['fields']);
- if ($imageField === false) {
- // no image field...
- return false;
- }
-
- $modelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $this->_addConstant('DATA_IMAGE_PREPARED', '\'imagePrepared\'');
- $this->_addConstant('IMAGE_SIZE_ORIGINAL', '-1');
- $this->_addProperty('$imageQuality', 'public static $imageQuality = 85');
-
- $this->_addMethod('setImage', 'public', array('$upload' => 'XenForo_Upload $upload'), "
-
-if (!\$upload->isValid()) {
- throw new XenForo_Exception(\$upload->getErrors(), true);
-}
-
-if (!\$upload->isImage()) {
- throw new XenForo_Exception(new XenForo_Phrase('uploaded_file_is_not_valid_image'), true);
-};
-
-\$imageType = \$upload->getImageInfoField('type');
-if (!in_array(\$imageType, array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) {
- throw new XenForo_Exception(new XenForo_Phrase('uploaded_file_is_not_valid_image'), true);
-}
-
-\$this->setExtraData(self::DATA_IMAGE_PREPARED, \$this->_prepareImage(\$upload));
-\$this->set('{$imageField}', XenForo_Application::\$time);
-
- ");
-
- $this->_addMethod('_prepareImage', 'protected', array('$upload' => 'XenForo_Upload $upload'), "
-
-\$outputFiles = array();
-\$fileName = \$upload->getTempFile();
-\$imageType = \$upload->getImageInfoField('type');
-\$outputType = \$imageType;
-\$width = \$upload->getImageInfoField('width');
-\$height = \$upload->getImageInfoField('height');
-
-\$imageSizes = \$this->getImageSizes();
-reset(\$imageSizes);
-
-while (list(\$sizeCode, \$maxDimensions) = each(\$imageSizes)) {
- \$newTempFile = tempnam(XenForo_Helper_File::getTempDir(), 'xfa');
-
- if (\$maxDimensions == self::IMAGE_SIZE_ORIGINAL) {
- copy(\$fileName, \$newTempFile);
- } else {
- \$image = XenForo_Image_Abstract::createFromFile(\$fileName, \$imageType);
- if (!\$image) {
- continue;
- }
-
- \$image->thumbnailFixedShorterSide(\$maxDimensions);
- if (\$image->getWidth() > \$maxDimensions OR \$image->getHeight() > \$maxDimensions) {
- \$image->crop(0, 0, \$maxDimensions, \$maxDimensions);
- }
-
- \$image->output(\$outputType, \$newTempFile, self::\$imageQuality);
- unset(\$image);
- }
-
- \$outputFiles[\$sizeCode] = \$newTempFile;
-}
-
-if (count(\$outputFiles) != count(\$imageSizes)) {
- foreach (\$outputFiles AS \$tempFile) {
- if (\$tempFile != \$fileName) {
- @unlink(\$tempFile);
- }
- }
-
- throw new XenForo_Exception('Non-image passed in to _prepareImage');
-}
-
-return \$outputFiles;
-
- ");
-
- $this->_addMethod('_moveImages', 'protected', array('$uploaded'), "
-
-if (is_array(\$uploaded)) {
- \$data = \$this->getMergedData();
- foreach (\$uploaded as \$sizeCode => \$tempFile) {
- \$filePath = {$modelClassName}::getImageFilePath(\$data, \$sizeCode);
- \$directory = dirname(\$filePath);
-
- if (XenForo_Helper_File::createDirectory(\$directory, true)) {
- XenForo_Helper_File::safeRename(\$tempFile, \$filePath);
- }
- }
-}
-
- ");
-
- $this->_addMethod('_postSave', 'protected', array(), "
-
-\$uploaded = \$this->getExtraData(self::DATA_IMAGE_PREPARED);
-if (\$uploaded) {
- \$this->_moveImages(\$uploaded);
-
- if (\$this->isUpdate()) {
- // removes old image
- \$existingData = \$this->getMergedExistingData();
- foreach (array_keys(\$this->getImageSizes()) as \$sizeCode) {
- \$filePath = {$modelClassName}::getImageFilePath(\$existingData, \$sizeCode);
- @unlink(\$filePath);
- }
- }
-}
-
- ");
-
- $this->_addMethod('_postDelete', 'protected', array(), "
-
-\$existingData = \$this->getMergedExistingData();
-foreach (array_keys(\$this->getImageSizes()) as \$sizeCode) {
- \$filePath = {$modelClassName}::getImageFilePath(\$existingData, \$sizeCode);
- @unlink(\$filePath);
-}
-
- ");
-
- $this->_addMethod('getImageSizes', 'public', array(), "
-
-return array(
- 'x' => self::IMAGE_SIZE_ORIGINAL,
- 'l' => 96,
- 'm' => 48,
- 's' => 24
-);
-
- ");
-
- return true;
- }
-
- protected function _generatePhrasesCode()
- {
- if (!empty($this->_dataClass['phrases'])) {
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate phrases code for %s: too many fields in primary key',
- $this->_getClassName()
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- foreach ($this->_dataClass['phrases'] as $phraseType) {
- $camelCase = ucwords(str_replace('_', ' ', $phraseType));
- $constantName = self::generateDataPhraseConstant(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $phraseType
- );
- $modelClassName = DevHelper_Generator_Code_Model::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $getPhraseTitleFunction = DevHelper_Generator_Code_Model::generateGetPhraseTitleFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $phraseType
- );
-
- $this->_addConstant($constantName, "'phrase{$camelCase}'");
-
- $this->_addMethod('_postSave', 'protected', array(), "
-
-\$phrase{$camelCase} = \$this->getExtraData(self::{$constantName});
-if (\$phrase{$camelCase} !== null) {
- \$this->_insertOrUpdateMasterPhrase({$modelClassName}::{$getPhraseTitleFunction}(\$this->get('{$idField}')), \$phrase{$camelCase});
-}
-
- ");
-
- $this->_addMethod('_postDelete', 'protected', array(), "
-
-\$this->_deleteMasterPhrase({$modelClassName}::{$getPhraseTitleFunction}(\$this->get('{$idField}')));
-
- ");
- }
- }
- }
-
- protected function _generateParentCode()
- {
- $parentField = DevHelper_Generator_Db::getParentField($this->_dataClass['name'], $this->_dataClass['fields']);
- if ($parentField === false) {
- // no parent field...
- return;
- }
-
- $displayOrderField = false;
- $depthField = false;
- $lftField = false;
- $rgtField = false;
- foreach ($this->_dataClass['fields'] as $field) {
- if ($field['name'] == 'display_order') {
- $displayOrderField = $field['name'];
- } elseif ($field['name'] == 'depth') {
- $depthField = $field['name'];
- } elseif ($field['name'] == 'lft') {
- $lftField = $field['name'];
- } elseif ($field['name'] == 'rgt') {
- $rgtField = $field['name'];
- }
- }
- if (empty($displayOrderField) OR empty($depthField) OR empty($lftField) OR empty($rgtField)) {
- // no hierarchy fields
- return;
- }
-
- $tableName = DevHelper_Generator_Db::getTableName($this->_config, $this->_dataClass['name']);
- $rebuildStructureFunctionName = DevHelper_Generator_Code_Model::generateRebuildStructureFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $titleFieldPostSaveConditions = '';
- if (!empty($this->_dataClass['title_field']) AND !is_array($this->_dataClass['title_field'])) {
- $titleFieldPostSaveConditions = "\n OR \$this->isChanged('{$this->_dataClass['title_field']}')";
- }
-
- $this->_addMethod('_postSave', 'protected', array(), "
-
-if (\$this->isInsert()
- OR \$this->isChanged('{$displayOrderField}')
- OR \$this->isChanged('{$parentField}'){$titleFieldPostSaveConditions}
-) {
- \$this->_get{$this->_dataClass['camelCase']}Model()->{$rebuildStructureFunctionName}();
-}
-
- ");
-
- $this->_addMethod('_postDelete', 'protected', array(), "
-
-\$this->_db->update('{$tableName}',
- array('{$parentField}' => \$this->get('{$parentField}')),
- '{$parentField} = ' . \$this->_db->quote(\$this->get('{$parentField}'))
-);
-
-\$this->_get{$this->_dataClass['camelCase']}Model()->{$rebuildStructureFunctionName}();
-
- ");
- }
-
- protected function _getClassName()
- {
- return self::getClassName($this->_addOn, $this->_config, $this->_dataClass);
- }
-
- public static function generate(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $g = new self($addOn, $config, $dataClass);
-
- return array(
- $g->_getClassName(),
- $g->_generate()
- );
- }
-
- public static function getClassName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return DevHelper_Generator_File::getClassName(
- $addOn['addon_id'],
- 'DataWriter_' . $dataClass['camelCase'],
- $config
- );
- }
-
- public static function generateDataPhraseConstant(
- array $addOn,
- DevHelper_Config_Base $config,
- array $dataClass,
- $phraseType
- ) {
- return 'DATA_PHRASE_' . strtoupper($phraseType);
- }
-}
diff --git a/library/DevHelper/Generator/Code/Installer.php b/library/DevHelper/Generator/Code/Installer.php
deleted file mode 100755
index 69e8818..0000000
--- a/library/DevHelper/Generator/Code/Installer.php
+++ /dev/null
@@ -1,133 +0,0 @@
-getDataClasses();
- foreach ($dataClasses as $dataClass) {
- $table = array();
- $table['createQuery'] = DevHelper_Generator_Db::createTable($config, $dataClass);
- $table['dropQuery'] = DevHelper_Generator_Db::dropTable($config, $dataClass);
-
- $tables[$dataClass['name']] = $table;
- }
- $tables = DevHelper_Generator_File::varExport($tables);
-
- $patches = array();
- $dataPatches = $config->getDataPatches();
- foreach ($dataPatches as $table => $tablePatches) {
- foreach ($tablePatches as $dataPatch) {
- $patch = array();
- $patch['table'] = $table;
- $patch['tableCheckQuery'] = DevHelper_Generator_Db::showTables($config, $table);
-
- if (!empty($dataPatch['index'])) {
- $patch['index'] = $dataPatch['name'];
- $patch['checkQuery'] = DevHelper_Generator_Db::showIndexes($config, $table, $dataPatch);
- $patch['addQuery'] = DevHelper_Generator_Db::alterTableAddIndex($config, $table, $dataPatch);
- $patch['dropQuery'] = DevHelper_Generator_Db::alterTableDropIndex($config, $table, $dataPatch);
- } else {
- $patch['field'] = $dataPatch['name'];
- $patch['checkQuery'] = DevHelper_Generator_Db::showColumns($config, $table, $dataPatch);
- $patch['addQuery'] = DevHelper_Generator_Db::alterTableAddColumn($config, $table, $dataPatch);
- $patch['modifyQuery'] = DevHelper_Generator_Db::alterTableModifyColumn($config, $table, $dataPatch);
- $patch['dropQuery'] = DevHelper_Generator_Db::alterTableDropColumn($config, $table, $dataPatch);
- }
-
- $patches[] = $patch;
- }
- }
- $patches = DevHelper_Generator_File::varExport($patches);
-
- $commentAutoGeneratedStart = DevHelper_Generator_File::COMMENT_AUTO_GENERATED_START;
- $commentAutoGeneratedEnd = DevHelper_Generator_File::COMMENT_AUTO_GENERATED_END;
-
- $contents = <<query(\$table['createQuery']);
- }
-
- foreach (self::\$_patches as \$patch) {
- \$tableExisted = \$db->fetchOne(\$patch['tableCheckQuery']);
- if (empty(\$tableExisted)) {
- continue;
- }
-
- \$existed = \$db->fetchOne(\$patch['checkQuery']);
- if (empty(\$existed)) {
- \$db->query(\$patch['addQuery']);
- } elseif (!empty(\$patch['modifyQuery'])) {
- \$db->query(\$patch['modifyQuery']);
- }
- }
-
- self::installCustomized(\$existingAddOn, \$addOnData);
- }
-
- public static function uninstall()
- {
- \$db = XenForo_Application::get('db');
-
- foreach (self::\$_patches as \$patch) {
- \$tableExisted = \$db->fetchOne(\$patch['tableCheckQuery']);
- if (empty(\$tableExisted)) {
- continue;
- }
-
- \$existed = \$db->fetchOne(\$patch['checkQuery']);
- if (!empty(\$existed)) {
- \$db->query(\$patch['dropQuery']);
- }
- }
-
- foreach (self::\$_tables as \$table) {
- \$db->query(\$table['dropQuery']);
- }
-
- self::uninstallCustomized();
- }
-
- $commentAutoGeneratedEnd
-
- public static function installCustomized(\$existingAddOn, \$addOnData)
- {
- // customized install script goes here
- }
-
- public static function uninstallCustomized()
- {
- // customized uninstall script goes here
- }
-
-}
-
-EOF;
-
- return array(
- $className,
- $contents
- );
- }
-
- public static function getClassName(array $addOn, DevHelper_Config_Base $config)
- {
- return DevHelper_Generator_File::getClassName($addOn['addon_id'], 'Installer', $config);
- }
-}
diff --git a/library/DevHelper/Generator/Code/Listener.php b/library/DevHelper/Generator/Code/Listener.php
deleted file mode 100644
index 2a73270..0000000
--- a/library/DevHelper/Generator/Code/Listener.php
+++ /dev/null
@@ -1,111 +0,0 @@
-setImportMode(true);
- $dw->bulkSet(array(
- 'event_id' => 'file_health_check',
- 'callback_class' => self::getClassName($addOn, $config),
- 'callback_method' => 'file_health_check',
- 'addon_id' => $addOn['addon_id'],
- ));
- return $dw->save();
- }
-
- return false;
- }
-
- public static function generateLoadClass($realClazz, array $addOn, DevHelper_Config_Base $config)
- {
- $method = sprintf('load_class_%s', $realClazz);
- if (strlen($method) > 50) {
- $method = sprintf('load_class_%s', md5($realClazz));
- }
-
- $existingContents = self::_getClassContents($addOn, $config);
- $existingMethods = DevHelper_Helper_Php::extractMethods($existingContents);
- if (in_array($method, $existingMethods, true)) {
- return $method;
- }
-
- $ourClazz = sprintf('%s_%s', $config->getClassPrefix(), $realClazz);
- $methodCode = <<_addOn = $addOn;
- $this->_config = $config;
- $this->_dataClass = $dataClass;
- }
-
- protected function _generate()
- {
- $className = $this->_getClassName();
- $tableName = DevHelper_Generator_Db::getTableName($this->_config, $this->_dataClass['name']);
- $idField = '';
- if (count($this->_dataClass['primaryKey']) == 1) {
- $idField = reset($this->_dataClass['primaryKey']);
- }
- $getFunctionName = self::generateGetDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
- $countFunctionName = self::generateCountDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
-
- $tableAlias = $this->_dataClass['name'];
- if (in_array($tableAlias, array(
- 'group',
- 'join',
- 'order'
- ))) {
- $tableAlias = '_' . $tableAlias;
- }
-
- $variableName = self::getVariableName($this->_addOn, $this->_config, $this->_dataClass);
- $variableNamePlural = self::getVariableNamePlural($this->_addOn, $this->_config, $this->_dataClass);
- $conditionFields = DevHelper_Generator_Db::getConditionFields($this->_dataClass['fields']);
-
- $this->_setClassName($className);
- $this->_setBaseClass('XenForo_Model');
-
- $this->_addCustomizableMethod("_{$getFunctionName}Customized", 'protected', array(
- 'array &$data',
- 'array $fetchOptions',
- ));
- $this->_addCustomizableMethod(
- "_prepare{$this->_dataClass['camelCase']}ConditionsCustomized",
- 'protected',
- array(
- 'array &$sqlConditions',
- 'array $conditions',
- 'array $fetchOptions',
- )
- );
- $this->_addCustomizableMethod(
- "_prepare{$this->_dataClass['camelCase']}FetchOptionsCustomized",
- 'protected',
- array(
- '&$selectFields',
- '&$joinTables',
- 'array $fetchOptions',
- )
- );
- $this->_addCustomizableMethod(
- "_prepare{$this->_dataClass['camelCase']}OrderOptionsCustomized",
- 'protected',
- array(
- 'array &$choices',
- 'array &$fetchOptions',
- )
- );
-
- if (!empty($idField)) {
- $this->_addMethod('getList', 'public', array(
- '$conditions' => 'array $conditions = array()',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\${$variableNamePlural} = \$this->{$getFunctionName}(\$conditions, \$fetchOptions);
-\$list = array();
-
-foreach (\${$variableNamePlural} as \$id => \${$variableName}) {
- \$list[\$id] = \${$variableName}" . (
- empty($this->_dataClass['title_field']) ?
- ("['{$idField}']") :
- (
- is_array($this->_dataClass['title_field']) ?
- ("['{$this->_dataClass['title_field'][0]}']['{$this->_dataClass['title_field'][1]}']") :
- ("['{$this->_dataClass['title_field']}']")
- )
- ) . ";
-}
-
-return \$list;
-
- ");
-
- $this->_addMethod("get{$this->_dataClass['camelCase']}ById", 'public', array(
- '$id',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\${$variableNamePlural} = \$this->{$getFunctionName}(array('{$idField}' => \$id), \$fetchOptions);
-
-return reset(\${$variableNamePlural});
-
- ");
-
- $this->_addMethod("get{$this->_dataClass['camelCase']}IdsInRange", 'public', array(
- '$start',
- '$limit',
- ), "
-
-\$db = \$this->_getDb();
-
-return \$db->fetchCol(\$db->limit('
- SELECT {$idField}
- FROM {$tableName}
- WHERE {$idField} > ?
- ORDER BY {$idField}
-', \$limit), \$start);
-
- ");
- }
-
- $this->_addMethod($getFunctionName, 'public', array(
- '$conditions' => 'array $conditions = array()',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\$whereConditions = \$this->prepare{$this->_dataClass['camelCase']}Conditions(\$conditions, \$fetchOptions);
-
-\$orderClause = \$this->prepare{$this->_dataClass['camelCase']}OrderOptions(\$fetchOptions);
-\$joinOptions = \$this->prepare{$this->_dataClass['camelCase']}FetchOptions(\$fetchOptions);
-\$limitOptions = \$this->prepareLimitFetchOptions(\$fetchOptions);
-
-\${$variableNamePlural} = \$this->" . (!empty($idField) ? "fetchAllKeyed" : "_getDb()->fetchAll") . "(\$this->limitQueryResults(\"
- SELECT {$tableAlias}.*
- \$joinOptions[selectFields]
- FROM `{$tableName}` AS {$tableAlias}
- \$joinOptions[joinTables]
- WHERE \$whereConditions
- \$orderClause
- \", \$limitOptions['limit'], \$limitOptions['offset']
-)" . (!empty($idField) ? ", '{$idField}'" : "") . ");
-
- ", '001');
-
- $this->_addMethod($getFunctionName, 'public', array(
- '$conditions' => 'array $conditions = array()',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\$this->_{$getFunctionName}Customized(\${$variableNamePlural}, \$fetchOptions);
-
-return \${$variableNamePlural};
-
- ", '999');
-
- $this->_addMethod($countFunctionName, 'public', array(
- '$conditions' => 'array $conditions = array()',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\$whereConditions = \$this->prepare{$this->_dataClass['camelCase']}Conditions(\$conditions, \$fetchOptions);
-
-\$orderClause = \$this->prepare{$this->_dataClass['camelCase']}OrderOptions(\$fetchOptions);
-\$joinOptions = \$this->prepare{$this->_dataClass['camelCase']}FetchOptions(\$fetchOptions);
-\$limitOptions = \$this->prepareLimitFetchOptions(\$fetchOptions);
-
-return \$this->_getDb()->fetchOne(\"
- SELECT COUNT(*)
- FROM `{$tableName}` AS {$tableAlias}
- \$joinOptions[joinTables]
- WHERE \$whereConditions
-\");
-
- ");
-
- $this->_addMethod("prepare{$this->_dataClass['camelCase']}Conditions", 'public', array(
- '$conditions' => 'array $conditions = array()',
- '$fetchOptions' => 'array $fetchOptions = array()',
- ), "
-
-\$sqlConditions = array();
-\$db = \$this->_getDb();
-
- ");
-
- foreach ($conditionFields as $conditionField) {
- $this->_addMethod("prepare{$this->_dataClass['camelCase']}Conditions", '', array(), "
-
-if (isset(\$conditions['{$conditionField}'])) {
- if (is_array(\$conditions['{$conditionField}'])) {
- if (!empty(\$conditions['{$conditionField}'])) {
- // only use IN condition if the array is not empty (nasty!)
- \$sqlConditions[] = \"{$tableAlias}.{$conditionField} IN (\" . \$db->quote(\$conditions['{$conditionField}']) . \")\";
- }
- } else {
- \$sqlConditions[] = \"{$tableAlias}.{$conditionField} = \" . \$db->quote(\$conditions['{$conditionField}']);
- }
-}
-
- ");
- }
-
- $this->_addMethod("prepare{$this->_dataClass['camelCase']}Conditions", '', array(), "
-
-\$this->_prepare{$this->_dataClass['camelCase']}ConditionsCustomized(\$sqlConditions, \$conditions, \$fetchOptions);
-
-return \$this->getConditionsForClause(\$sqlConditions);
-
- ");
-
- $this->_addMethod(
- "prepare{$this->_dataClass['camelCase']}FetchOptions",
- 'public',
- array('$fetchOptions' => 'array $fetchOptions = array()'),
- "
-
-\$selectFields = '';
-\$joinTables = '';
-
-\$this->_prepare{$this->_dataClass['camelCase']}FetchOptionsCustomized(\$selectFields, \$joinTables, \$fetchOptions);
-
-return array(
- 'selectFields' => \$selectFields,
- 'joinTables' => \$joinTables
-);
-
- "
- );
-
- $orderChoices = array();
- if (isset($this->_dataClass['fields']['display_order'])) {
- if (isset($this->_dataClass['fields']['lft'])) {
- $orderChoices['display_order'] = sprintf('%s.lft', $tableAlias);
- } else {
- $orderChoices['display_order'] = sprintf('%s.display_order', $tableAlias);
- }
- }
- $orderChoices = DevHelper_Generator_File::varExport($orderChoices);
-
- $this->_addMethod("prepare{$this->_dataClass['camelCase']}OrderOptions", 'public', array(
- '$fetchOptions' => 'array $fetchOptions = array()',
- '$defaultOrderSql' => '$defaultOrderSql = \'\'',
- ), "
-
-\$choices = {$orderChoices};
-
-\$this->_prepare{$this->_dataClass['camelCase']}OrderOptionsCustomized(\$choices, \$fetchOptions);
-
-return \$this->getOrderByClause(\$choices, \$fetchOptions, \$defaultOrderSql);
-
- ");
-
- $this->_generateImageCode();
- $this->_generatePhrasesCode();
- $this->_generateOptionsCode();
- $this->_generateParentCode();
-
- return parent::_generate();
- }
-
- protected function _generateImageCode()
- {
- $imageField = DevHelper_Generator_Db::getImageField($this->_dataClass['fields']);
- if ($imageField === false) {
- // no image field...
- return '';
- }
-
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate image code for %s: too many fields in primary key',
- $this->_getClassName()
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- $getFunctionName = self::generateGetDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
- $variableName = self::getVariableName($this->_addOn, $this->_config, $this->_dataClass);
- $variableNamePlural = self::getVariableNamePlural($this->_addOn, $this->_config, $this->_dataClass);
- $dwClassName = DevHelper_Generator_Code_DataWriter::getClassName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $configPrefix = $this->_config->getPrefix();
- $imagePath = "{$configPrefix}/{$this->_dataClass['camelCase']}";
- $imagePath = strtolower($imagePath);
-
- $this->_addMethod($getFunctionName, '', array(), "
-
-// build image urls and make them ready for all the records
-\$imageSizes = XenForo_DataWriter::create('{$dwClassName}')->getImageSizes();
-foreach (\${$variableNamePlural} as &\${$variableName}) {
- \${$variableName}['images'] = array();
- if (!empty(\${$variableName}['{$imageField}'])) {
- foreach (\$imageSizes as \$imageSizeCode => \$imageSize) {
- \${$variableName}['images'][\$imageSizeCode] = \$this->getImageUrl(\${$variableName}, \$imageSizeCode);
- }
- }
-}
-
- ", '100');
-
- $this->_addMethod('getImageFilePath', 'public static', array(
- sprintf('$%s', $variableName) => sprintf('array $%s', $variableName),
- '$size' => '$size = \'l\'',
- ), "
-
-\$internal = self::_getImageInternal(\${$variableName}, \$size);
-
-if (!empty(\$internal)) {
- return XenForo_Helper_File::getExternalDataPath() . \$internal;
-} else {
- return '';
-}
-
- ");
-
- $this->_addMethod('getImageUrl', 'public static', array(
- sprintf('$%s', $variableName) => sprintf('array $%s', $variableName),
- '$size' => '$size = \'l\'',
- ), "
-
-\$internal = self::_getImageInternal(\${$variableName}, \$size);
-
-if (!empty(\$internal)) {
- return XenForo_Application::\$externalDataUrl . \$internal;
-} else {
- return '';
-}
-
- ");
-
- $this->_addMethod('_getImageInternal', 'protected static', array(
- sprintf('$%s', $variableName) => sprintf('array $%s', $variableName),
- '$size',
- ), "
-
-if (empty(\${$variableName}['{$idField}']) OR empty(\${$variableName}['{$imageField}'])) {
- return '';
-}
-
-return '/{$imagePath}/' . \${$variableName}['{$idField}'] . '_' . \${$variableName}['{$imageField}'] . strtolower(\$size) . '.jpg';
-
- ");
-
- return true;
- }
-
- protected function _generatePhrasesCode()
- {
- $variableName = self::getVariableName($this->_addOn, $this->_config, $this->_dataClass);
- $variableNamePlural = self::getVariableNamePlural($this->_addOn, $this->_config, $this->_dataClass);
-
- if (!empty($this->_dataClass['phrases'])) {
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate phrases code for %s: too many fields in primary key',
- $this->_getClassName()
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- $statements = '';
-
- foreach ($this->_dataClass['phrases'] as $phraseType) {
- $getPhraseTitleFunction = self::generateGetPhraseTitleFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $phraseType
- );
- $phraseTitlePrefix = DevHelper_Generator_Phrase::getPhraseName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass,
- $this->_dataClass['name']
- ) . '_';
-
- $this->_addMethod($getPhraseTitleFunction, 'public static', array('$id'), "
-
-return \"{$phraseTitlePrefix}{\$id}_{$phraseType}\";
-
- ");
-
- $statements .= " '{$phraseType}' => new XenForo_Phrase(self::{$getPhraseTitleFunction}(\${$variableName}['{$idField}'])),\n";
- }
-
- $getFunctionName = self::generateGetDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
-
- $this->_addMethod($getFunctionName, '', array(), "
-
-// prepare the phrases
-foreach (\${$variableNamePlural} as &\${$variableName}) {
- \${$variableName}['phrases'] = array(
-{$statements} );
-}
-
- ", '300');
- }
- }
-
- protected function _generateOptionsCode()
- {
- $variableName = self::getVariableName($this->_addOn, $this->_config, $this->_dataClass);
- $variableNamePlural = self::getVariableNamePlural($this->_addOn, $this->_config, $this->_dataClass);
- $optionsFields = DevHelper_Generator_Db::getOptionsFields($this->_dataClass['fields']);
-
- if (!empty($optionsFields)) {
- $statements = '';
-
- foreach ($optionsFields as $optionsField) {
- $statements .= " \${$variableName}['{$optionsField}'] = @unserialize(\${$variableName}['{$optionsField}']);\n";
- $statements .= " if (empty(\${$variableName}['{$optionsField}'])) \${$variableName}['{$optionsField}'] = array();\n";
- }
-
- $getFunctionName = self::generateGetDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
-
- $this->_addMethod($getFunctionName, '', array(), "
-
-// parse all the options fields
-foreach (\${$variableNamePlural} as &\${$variableName}) {
-{$statements}}
-
- ", '400');
- }
- }
-
- protected function _generateParentCode()
- {
- $parentField = DevHelper_Generator_Db::getParentField($this->_dataClass['name'], $this->_dataClass['fields']);
- if ($parentField === false) {
- // no parent field...
- return;
- }
-
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf(
- 'Cannot generate parent code for %s: too many fields in primary key',
- $this->_getClassName()
- ));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- $displayOrderField = false;
- $depthField = false;
- $lftField = false;
- $rgtField = false;
- $breadcrumbField = DevHelper_Generator_Db::getBreadcrumbField(
- $this->_dataClass['name'],
- $this->_dataClass['fields']
- );
- ;
- foreach ($this->_dataClass['fields'] as $field) {
- if ($field['name'] == 'display_order') {
- $displayOrderField = $field['name'];
- } elseif ($field['name'] == 'depth') {
- $depthField = $field['name'];
- } elseif ($field['name'] == 'lft') {
- $lftField = $field['name'];
- } elseif ($field['name'] == 'rgt') {
- $rgtField = $field['name'];
- }
- }
- if (empty($displayOrderField) OR empty($depthField) OR empty($lftField) OR empty($rgtField)) {
- // no hierarchy fields
- return;
- }
-
- $tableName = DevHelper_Generator_Db::getTableName($this->_config, $this->_dataClass['name']);
- $getFunctionName = self::generateGetDataFunctionName($this->_addOn, $this->_config, $this->_dataClass);
- $variableName = self::getVariableName($this->_addOn, $this->_config, $this->_dataClass);
- $variableNamePlural = self::getVariableNamePlural($this->_addOn, $this->_config, $this->_dataClass);
-
- $rebuildStructureFunctionName = self::generateRebuildStructureFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
- $getStructureChangesFunctionName = '_getStructureChanges';
- $groupByParentsFunctionName = self::generateGroupByParentsFunctionName(
- $this->_addOn,
- $this->_config,
- $this->_dataClass
- );
-
- $this->_addMethod($rebuildStructureFunctionName, 'public', array(), "
-
-\$grouped = \$this->{$groupByParentsFunctionName}(\$this->{$getFunctionName}(array(), array('order' => '{$displayOrderField}')));
-
-\$db = \$this->_getDb();
-XenForo_Db::beginTransaction(\$db);
-
-\$changes = \$this->{$getStructureChangesFunctionName}(\$grouped);
-foreach (\$changes AS \$id => \$oneChanges) {
- \$db->update('{$tableName}', \$oneChanges, '{$idField} = ' . \$db->quote(\$id));
-}
-
-XenForo_Db::commit(\$db);
-
-return \$changes;
-
- ");
-
- $titleFieldBreadcrumb = '';
- if (!empty($this->_dataClass['title_field']) AND !is_array($this->_dataClass['title_field'])) {
- $titleFieldBreadcrumb = "\n '{$this->_dataClass['title_field']}' => " .
- "\${$variableName}['{$this->_dataClass['title_field']}'],";
- }
-
- $breadcrumbStatements = '';
- if (!empty($breadcrumbField)) {
- $breadcrumbStatements = "\n if (\${$variableName}['category_breadcrumb'] != \$serializedBreadcrumb) {";
- $breadcrumbStatements .= "\n \$thisChanges['category_breadcrumb'] = \$serializedBreadcrumb;";
- $breadcrumbStatements .= "\n }";
- }
-
- $this->_addMethod($getStructureChangesFunctionName, 'protected', array(
- '$grouped' => 'array $grouped',
- '$parentId' => '$parentId = 0',
- '$depth' => '$depth = 0',
- '$startPosition' => '$startPosition = 1',
- 'nextPosition' => '&$nextPosition = 0',
- '$breadcrumb' => 'array $breadcrumb = array()',
- ), "
-
-\$nextPosition = \$startPosition;
-
-if (!isset(\$grouped[\$parentId])) {
- return array();
-}
-
-\$changes = array();
-\$serializedBreadcrumb = serialize(\$breadcrumb);
-
-foreach (\$grouped[\$parentId] AS \$id => \${$variableName}) {
- \$left = \$nextPosition;
- \$nextPosition++;
-
- \$thisBreadcrumb = \$breadcrumb + array(
- \$id => array(
- '{$idField}' => \$id,{$titleFieldBreadcrumb}
- '{$parentField}' => \${$variableName}['{$parentField}'],
- )
- );
-
- \$changes += \$this->{$getStructureChangesFunctionName}(
- \$grouped,
- \$id,
- \$depth + 1,
- \$nextPosition,
- \$nextPosition,
- \$thisBreadcrumb
- );
-
- \$thisChanges = array();
- if (\${$variableName}['depth'] != \$depth) {
- \$thisChanges['depth'] = \$depth;
- }
- if (\${$variableName}['lft'] != \$left) {
- \$thisChanges['lft'] = \$left;
- }
- if (\${$variableName}['rgt'] != \$nextPosition) {
- \$thisChanges['rgt'] = \$nextPosition;
- }{$breadcrumbStatements}
-
- if (!empty(\$thisChanges)) {
- \$changes[\$id] = \$thisChanges;
- }
-
- \$nextPosition++;
-}
-
-return \$changes;
-
- ");
-
- $this->_addMethod(
- $groupByParentsFunctionName,
- 'public',
- array(sprintf('$%s', $variableNamePlural) => sprintf('array $%s', $variableNamePlural)),
- "
-
-\$grouped = array();
-foreach (\${$variableNamePlural} AS \${$variableName}) {
- \$grouped[\${$variableName}['{$parentField}']][\${$variableName}['{$idField}']] = \${$variableName};
-}
-
-return \$grouped;
-
- "
- );
- }
-
- protected function _getClassName()
- {
- return self::getClassName($this->_addOn, $this->_config, $this->_dataClass);
- }
-
- public static function generate(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $g = new self($addOn, $config, $dataClass);
-
- return array(
- $g->_getClassName(),
- $g->_generate()
- );
- }
-
- public static function getClassName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return DevHelper_Generator_File::getClassName($addOn['addon_id'], 'Model_' . $dataClass['camelCase'], $config);
- }
-
- public static function getVariableName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $variableName = strtolower(substr($dataClass['camelCase'], 0, 1)) . substr($dataClass['camelCase'], 1);
- $variableNamePlural = self::getVariableNamePlural($addOn, $config, $dataClass);
-
- if ($variableName === $variableNamePlural) {
- $variableName = '_' . $variableName;
- }
-
- return $variableName;
- }
-
- public static function getVariableNamePlural(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $variableNamePlural = (empty($dataClass['camelCasePlural']) ? ('All' . $dataClass['camelCase']) : ($dataClass['camelCasePlural']));
-
- return strtolower(substr($variableNamePlural, 0, 1)) . substr($variableNamePlural, 1);
- }
-
- public static function generateGetDataFunctionName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return 'get' . (empty($dataClass['camelCasePlural']) ? ('All' . $dataClass['camelCase']) : $dataClass['camelCasePlural']);
- }
-
- public static function generateCountDataFunctionName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return 'count' . (empty($dataClass['camelCasePlural']) ? ('All' . $dataClass['camelCase']) : $dataClass['camelCasePlural']);
- }
-
- public static function generateRebuildStructureFunctionName(
- array $addOn,
- DevHelper_Config_Base $config,
- array $dataClass
- ) {
- return 'rebuild' . $dataClass['camelCase'] . 'Structure';
- }
-
- public static function generateGroupByParentsFunctionName(
- array $addOn,
- DevHelper_Config_Base $config,
- array $dataClass
- ) {
- return 'group' . (empty($dataClass['camelCasePlural']) ? ('All' . $dataClass['camelCase']) : $dataClass['camelCasePlural']) . 'ByParents';
- }
-
- public static function generateGetPhraseTitleFunctionName(
- array $addOn,
- DevHelper_Config_Base $config,
- array $dataClass,
- $phraseType
- ) {
- $camelCase = ucwords(str_replace('_', ' ', $phraseType));
- return 'getPhraseTitleFor' . $camelCase;
- }
-}
diff --git a/library/DevHelper/Generator/Code/RoutePrefixAdmin.php b/library/DevHelper/Generator/Code/RoutePrefixAdmin.php
deleted file mode 100755
index 5590228..0000000
--- a/library/DevHelper/Generator/Code/RoutePrefixAdmin.php
+++ /dev/null
@@ -1,131 +0,0 @@
-_addOn = $addOn;
- $this->_config = $config;
- $this->_dataClass = $dataClass;
- $this->_info = $info;
- }
-
- protected function _generate()
- {
- $className = $this->_getClassName();
-
- if (count($this->_dataClass['primaryKey']) > 1) {
- throw new XenForo_Exception(sprintf('Cannot generate %s: too many fields in primary key', $className));
- }
- $idField = reset($this->_dataClass['primaryKey']);
-
- // create the route prefix first
- /** @var XenForo_Model_RoutePrefix $routePrefixModel */
- $routePrefixModel = XenForo_Model::create('XenForo_Model_RoutePrefix');
- $existed = $routePrefixModel->getPrefixesByRouteType('admin');
- foreach ($existed as $routePrefix) {
- if ($routePrefix['original_prefix'] == $this->_info['routePrefix'] OR $routePrefix['route_class'] == $className) {
- // delete duplicated route prefix
- $dw = XenForo_DataWriter::create('XenForo_DataWriter_RoutePrefix');
- $dw->setExistingData($routePrefix);
- $dw->delete();
- }
- }
-
- eval("class $className {}");
- $dw = XenForo_DataWriter::create('XenForo_DataWriter_RoutePrefix');
- $dw->bulkSet(array(
- 'original_prefix' => $this->_info['routePrefix'],
- 'route_type' => 'admin',
- 'route_class' => $className,
- 'build_link' => 'data_only',
- 'addon_id' => $this->_addOn['addon_id'],
- ));
- $dw->save();
- // finished creating our route prefix
-
- $this->_setClassName($className);
- $this->_addInterface('XenForo_Route_Interface');
-
- $this->_addMethod('match', 'public', array(
- '$routePath',
- '$request' => 'Zend_Controller_Request_Http $request',
- '$router' => 'XenForo_Router $router',
- ), "
-
-if (in_array(\$routePath, array('add', 'save'))) {
- \$action = \$routePath;
-} else {
- \$action = \$router->resolveActionWithIntegerParam(\$routePath, \$request, '{$idField}');
-}
-return \$router->getRouteMatch('{$this->_info['controller']}', \$action, '{$this->_info['majorSection']}');
-
- ");
-
- $this->_addMethod('buildLink', 'public', array(
- '$originalPrefix',
- '$outputPrefix',
- '$action',
- '$extension',
- '$data',
- '$extraParams' => 'array &$extraParams',
- ), "
-
-if (is_array(\$data)) {
- return XenForo_Link::buildBasicLinkWithIntegerParam(\$outputPrefix, \$action, \$extension, \$data, '{$idField}');
-} else {
- return XenForo_Link::buildBasicLink(\$outputPrefix, \$action, \$extension);
-}
-
- ");
-
- return parent::_generate();
- }
-
- protected function _getClassName()
- {
- return self::getClassName($this->_addOn, $this->_config, $this->_dataClass);
- }
-
- public static function generate(array $addOn, DevHelper_Config_Base $config, array $dataClass, array $info)
- {
- $g = new self($addOn, $config, $dataClass, $info);
-
- return array(
- $g->_getClassName(),
- $g->_generate()
- );
- }
-
- public static function getClassName(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- return DevHelper_Generator_File::getClassName(
- $addOn['addon_id'],
- 'Route_PrefixAdmin_' . $dataClass['camelCase'],
- $config
- );
- }
-
- public static function getRoutePrefix(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $className = self::getClassName($addOn, $config, $dataClass);
-
- /** @var XenForo_Model_RoutePrefix $routePrefixModel */
- $routePrefixModel = XenForo_Model::create('XenForo_Model_RoutePrefix');
- $routePrefixes = $routePrefixModel->getPrefixesByAddOnGroupedByRouteType($addOn['addon_id']);
- if (!empty($routePrefixes['admin'])) {
- foreach ($routePrefixes['admin'] as $routePrefix) {
- if ($routePrefix['route_class'] == $className) {
- return $routePrefix['original_prefix'];
- }
- }
- }
-
- return strtolower($addOn['addon_id'] . '-' . $dataClass['name']);
- }
-}
diff --git a/library/DevHelper/Generator/Code/XenForoConfig.php b/library/DevHelper/Generator/Code/XenForoConfig.php
deleted file mode 100644
index 16d445f..0000000
--- a/library/DevHelper/Generator/Code/XenForoConfig.php
+++ /dev/null
@@ -1,55 +0,0 @@
-getRootDir() . '/library/config.php';
- $originalContents = file_get_contents($path);
-
- $keyParts = explode('.', $key);
- $varNamePattern = '#(\n|^)(\\$config';
- foreach ($keyParts as $i => $keyPart) {
- // try to match the quote
- $varNamePattern .= '\\[([\'"]?)'
- // then the key
- . preg_quote($keyPart, '#')
- // then match the previously matched quote
- . '\\' . ($i + 3) . '\\]';
- }
- $varNamePattern .= ').+(\n|$)#';
-
- $candidates = array();
- $offset = 0;
- while (true) {
- if (!preg_match($varNamePattern, $originalContents, $matches, PREG_OFFSET_CAPTURE, $offset)) {
- break;
- }
-
- $offset = $matches[0][1] + strlen($matches[0][0]);
- $candidates[] = $matches;
- }
-
- if (count($candidates) > 1) {
- throw new XenForo_Exception(sprintf('count($candidates) = %d', count($candidates)));
- }
-
- $phpStatement = sprintf(
- '$config["%s"] = %s;',
- implode('"]["', $keyParts),
- var_export($value, true)
- );
- if (count($candidates) === 1) {
- $matches = reset($candidates);
-
- $replacement = $matches[1][0] . $phpStatement . $matches[5][0];
- $contents = substr_replace($originalContents, $replacement, $matches[0][1], strlen($matches[0][0]));
- } else {
- $contents = $originalContents . "\n\n" . $phpStatement;
- }
-
- DevHelper_Generator_File::writeFile($path, $contents, true, false);
- }
-}
diff --git a/library/DevHelper/Generator/Db.php b/library/DevHelper/Generator/Db.php
deleted file mode 100755
index cad8d7e..0000000
--- a/library/DevHelper/Generator/Db.php
+++ /dev/null
@@ -1,360 +0,0 @@
-getDataPatches() as $patchTableName => $patchTablePatches) {
- if ($patchTableName !== $tableName) {
- continue;
- }
-
- foreach ($patchTablePatches as $dataPatch) {
- if (!empty($dataPatch['index'])) {
- $indexConfigs[$dataPatch['name']] = $dataPatch;
- } else {
- $fieldConfigs[$dataPatch['name']] = $dataPatch;
- }
- }
- }
-
- $fields = array();
- foreach ($fieldConfigs as $field) {
- $fields[] = "`$field[name]` " . self::_getFieldDefinition($field);
- }
- $fields = implode("\n ,", $fields);
-
- if (!empty($dataClass['primaryKey'])) {
- $primaryKey = ", PRIMARY KEY (`" . implode('`,`', $dataClass['primaryKey']) . "`)";
- } else {
- $primaryKey = '';
- }
-
- $indeces = array();
- foreach ($indexConfigs as $index) {
- $indeces[] = self::_getIndexDefinition($index);
- }
- $indeces = implode("\n ,", $indeces);
- if (!empty($indeces)) {
- $indeces = ',' . $indeces;
- }
-
- $sql = <<getPrefix()) !== false
- ) {
- return $name;
- } else {
- return 'xf_' . self::getFieldName($config, $name, true);
- }
- }
-
- public static function getFieldName(DevHelper_Config_Base $config, $name, $ignoreDash = false)
- {
- if ($ignoreDash OR strpos($name, '_') === false) {
- return strtolower($config->getPrefix() . '_' . $name);
- } else {
- return strtolower($name);
- }
- }
-
- public static function getConditionFields(array $fields)
- {
- $conditionsFields = array();
-
- $intTypes = array(
- XenForo_DataWriter::TYPE_BOOLEAN,
- XenForo_DataWriter::TYPE_INT,
- XenForo_DataWriter::TYPE_UINT,
- XenForo_DataWriter::TYPE_UINT_FORCED,
- );
- $imageFields = self::getImageFields($fields);
-
- foreach ($fields as $field) {
- if (in_array($field['name'], $imageFields)) {
- continue;
- }
-
- if (in_array($field['type'], $intTypes)) {
- $conditionsFields[] = $field['name'];
- continue;
- }
-
- if ($field['type'] == 'string' AND isset($field['length']) AND $field['length'] <= 255) {
- // this is a VARCHAR one
- $conditionsFields[] = $field['name'];
- continue;
- }
- }
-
- return $conditionsFields;
- }
-
- public static function getImageFields(array $fields)
- {
- $imageFields = array();
-
- foreach ($fields as $field) {
- if (substr($field['name'], -10) == 'image_date') {
- $imageFields[] = $field['name'];
- }
- }
-
- return $imageFields;
- }
-
- public static function getImageField(array $fields)
- {
- $imageFields = self::getImageFields($fields);
-
- if (count($imageFields) == 1) {
- // only return the image field if there is 1 image field
- // if there is no image fields or more than 1, simply ignore them all
- return $imageFields[0];
- } else {
- return false;
- }
- }
-
- public static function getOptionsFields(array $fields)
- {
- $optionsFields = array();
-
- foreach ($fields as $field) {
- if (substr($field['name'], -8) == '_options' AND $field['type'] == XenForo_DataWriter::TYPE_SERIALIZED) {
- $optionsFields[] = $field['name'];
- }
- }
-
- return $optionsFields;
- }
-
- public static function getParentField($className, array $fields)
- {
- $parentFieldNames = array(
- sprintf('%s_parent_id', $className),
- sprintf('parent_%s_id', $className),
- 'parent_id',
- );
-
- foreach ($fields as $field) {
- if (in_array($field['name'], $parentFieldNames)) {
- return $field['name'];
- }
- }
-
- return false;
- }
-
- public static function getBreadcrumbField($className, array $fields)
- {
- $breadcrumbFieldNames = array(
- sprintf('%s_breadcrumb', $className),
- 'breadcrumb',
- );
-
- foreach ($fields as $field) {
- if (in_array($field['name'], $breadcrumbFieldNames)) {
- return $field['name'];
- }
- }
-
- return false;
- }
-
- public static function getDataTypes()
- {
- return $types = array(
- XenForo_DataWriter::TYPE_BOOLEAN,
- XenForo_DataWriter::TYPE_STRING,
- XenForo_DataWriter::TYPE_BINARY,
- XenForo_DataWriter::TYPE_INT,
- XenForo_DataWriter::TYPE_UINT,
- XenForo_DataWriter::TYPE_UINT_FORCED,
- XenForo_DataWriter::TYPE_FLOAT,
- XenForo_DataWriter::TYPE_SERIALIZED,
- );
- }
-
- protected static function _getFieldDefinition($field)
- {
- switch ($field['type']) {
- case XenForo_DataWriter::TYPE_BOOLEAN:
- $dbType = 'TINYINT(4) UNSIGNED';
- break;
- case XenForo_DataWriter::TYPE_STRING:
- if (!isset($field['length']) || $field['length'] > 255) {
- $dbType = 'TEXT';
- if (isset($field['length'])) {
- if ($field['length'] >= 4294967295) {
- $dbType = 'LONGTEXT';
- } elseif ($field['length'] >= 16777215) {
- $dbType = 'MEDIUMTEXT';
- }
- }
- if (isset($field['default'])) {
- // BLOB/TEXT column can't have a default value
- unset($field['default']);
- }
- } else {
- if (!empty($field['allowedValues'])) {
- // ENUM
- $dbType = 'ENUM (\'' . implode('\',\'', $field['allowedValues']) . '\')';
- } else {
- $dbType = 'VARCHAR(' . $field['length'] . ')';
- }
- }
- break;
- case XenForo_DataWriter::TYPE_BINARY:
- if (!isset($field['length']) || $field['length'] > 255) {
- $dbType = 'BLOB';
- if (isset($field['length'])) {
- if ($field['length'] >= 4294967295) {
- $dbType = 'LONGBLOB';
- } elseif ($field['length'] >= 16777215) {
- $dbType = 'MEDIUMBLOB';
- }
- }
- } else {
- $dbType = 'VARBINARY(' . $field['length'] . ')';
- }
- if (isset($field['default'])) {
- // BLOB/TEXT column can't have a default value
- unset($field['default']);
- }
- break;
- case XenForo_DataWriter::TYPE_INT:
- $dbType = 'INT(11)';
- if (isset($field['length'])) {
- if ($field['length'] == 4) {
- $dbType = 'TINYINT(' . $field['length'] . ')';
- }
- }
- break;
- case XenForo_DataWriter::TYPE_UINT:
- case XenForo_DataWriter::TYPE_UINT_FORCED:
- $dbType = 'INT(10) UNSIGNED';
- break;
- case XenForo_DataWriter::TYPE_FLOAT:
- $dbType = 'FLOAT';
- break;
- case 'money':
- $dbType = 'DECIMAL(13,4)';
- break;
- case XenForo_DataWriter::TYPE_SERIALIZED:
- default:
- $dbType = 'MEDIUMBLOB';
- if (isset($field['default'])) {
- unset($field['default']);
- }
- // BLOB/TEXT column can't have a default value
- break;
- }
-
- return $dbType . (!empty($field['required']) ? ' NOT NULL' : '') .
- (isset($field['default']) ? " DEFAULT '{$field['default']}'" : '') .
- (!empty($field['autoIncrement']) ? ' AUTO_INCREMENT' : '');
- }
-
- public static function getIndexTypes()
- {
- return array(
- 'NORMAL',
- 'UNIQUE',
- 'FULLTEXT',
- 'SPATIAL',
- );
- }
-
- protected static function _getIndexDefinition($index)
- {
- $indexName = $index['name'];
- $indexType = strtoupper($index['type']);
-
- $definition = ($indexType != 'NORMAL' ? ($index['type'] . ' ') : '')
- . "INDEX `{$indexName}` (`" . implode('`,`', $index['fields']) . "`)";
-
- return $definition;
- }
-}
diff --git a/library/DevHelper/Generator/File.php b/library/DevHelper/Generator/File.php
deleted file mode 100755
index 2e5b757..0000000
--- a/library/DevHelper/Generator/File.php
+++ /dev/null
@@ -1,747 +0,0 @@
-get(sprintf('DevHelper_%sConfigClass', $addOnId));
- if ($addOnId === 'devHelper') {
- $className = 'DevHelper_DevHelper_Config';
- }
- if (empty($className)) {
- $className = sprintf('%s_DevHelper_Config', $addOnId);
- }
- } else {
- if ($config === null) {
- throw new XenForo_Exception(sprintf(
- '%s requires $config when $subClassName=%s',
- __METHOD__,
- $subClassName
- ));
- }
-
- $className = rtrim(sprintf('%s_%s', $config->getClassPrefix(), $subClassName), '_');
- }
-
- $classNames[$hash] = $className;
- }
-
- return $classNames[$hash];
- }
-
- public static function getClassPath($className, DevHelper_Config_Base $config = null)
- {
- if ($config === null) {
- return DevHelper_Autoloader::getDevHelperInstance()->autoloaderClassToFile($className);
- } else {
- $configClass = get_class($config);
- $thisClassParts = explode('_', $className);
- $configClassParts = explode('_', $configClass);
-
- $configClassPath = self::getClassPath($configClass);
- $configClassPathParts = explode('/', $configClassPath);
- while ($thisClassParts[0] === $configClassParts[0]) {
- array_shift($thisClassParts);
- array_shift($configClassParts);
- }
- while (count($configClassParts) > 0) {
- array_pop($configClassParts);
- array_pop($configClassPathParts);
- }
-
- $path = sprintf('%s/%s.php', implode('/', $configClassPathParts), implode('/', $thisClassParts));
-
- return $path;
- }
- }
-
- public static function getAddOnIdPath(DevHelper_Config_Base $config)
- {
- $libraryPath = self::getLibraryPath($config);
- $path = $libraryPath . DIRECTORY_SEPARATOR .
- str_replace('_', DIRECTORY_SEPARATOR, $config->getClassPrefix());
-
- return $path;
- }
-
- public static function getLibraryPath(DevHelper_Config_Base $config)
- {
- $configClassPath = self::getClassPath(get_class($config));
- $path = $configClassPath;
-
- do {
- if (empty($path)) {
- throw new XenForo_Exception(sprintf('Cannot find library path for %s', get_class($config)));
- }
- $path = dirname($path);
- } while (basename($path) !== 'library');
-
- return $path;
- }
-
- public static function getAddOnXmlPath(
- array $addOn,
- array $exportAddOn = null,
- DevHelper_Config_Base $config = null
- ) {
- if ($config === null) {
- /** @var DevHelper_Model_Config $configModel */
- $configModel = XenForo_Model::create('DevHelper_Model_Config');
- $config = $configModel->loadAddOnConfig($addOn);
- }
-
- $addOnIdPath = self::getAddOnIdPath($config);
- $addOnId = (!empty($exportAddOn) ? $exportAddOn['addon_id'] : $addOn['addon_id']);
-
- return $addOnIdPath . '/addon-' . $addOnId . '.xml';
- }
-
- public static function getStyleXmlPath(
- array $addOn,
- array $style,
- DevHelper_Config_Base $config
- ) {
- $addOnIdPath = self::getAddOnIdPath($config);
-
- return $addOnIdPath . '/style-' . $style['title'] . '.xml';
- }
-
- public static function writeFile($path, $contents, $backUp, $isAutoGenerated)
- {
- $skip = false;
-
- if (file_exists($path)) {
- // existed file
- $oldContents = self::fileGetContents($path);
-
- if ($oldContents == $contents) {
- // same content
- $skip = true;
- } else {
- if ($backUp) {
- if (strpos($path, 'FileSums.php') !== false) {
- // writing FileSums.php
- // this file is generated so many times that it's annoying
- // so we will skip saving a copy of it...
- } else {
- copy($path, $path . '.' . XenForo_Application::$time . '.devhelper');
- }
- }
-
- if ($isAutoGenerated AND XenForo_Helper_File::getFileExtension($path) === 'php') {
- // different php content
- // try to replace the auto generated code only
- $startPosOld = strpos($oldContents, self::COMMENT_AUTO_GENERATED_START);
- $endPosOld = strpos($oldContents, self::COMMENT_AUTO_GENERATED_END, $startPosOld);
-
- if ($startPosOld !== false AND $endPosOld !== false AND $endPosOld > $startPosOld) {
- // found our comments in old contents
- $startPos = strpos($contents, self::COMMENT_AUTO_GENERATED_START);
- $endPos = strpos($contents, self::COMMENT_AUTO_GENERATED_END, $startPos);
-
- if ($startPos !== false AND $endPos !== false AND $endPos > $startPos) {
- // found our comments in new contents
- $replacement = substr($contents, $startPos, $endPos - $startPos);
- $start = $startPosOld;
- $length = $endPosOld - $startPosOld;
-
- $contents = substr_replace($oldContents, $replacement, $start, $length);
- }
- }
- }
- }
- }
-
- if (!$skip) {
- return self::filePutContents($path, $contents);
- } else {
- return 'skip';
- }
- }
-
- public static function writeClass($className, $contents, DevHelper_Config_Base $config = null)
- {
- $path = self::getClassPath($className, $config);
-
- self::writeFile($path, $contents, true, true);
-
- if (strpos($className, 'DevHelper_Generated') === false) {
- $backupClassName = self::_getBackupClassName($className);
- $backupPath = self::getClassPath($backupClassName, $config);
- self::writeFile($backupPath . '.devhelper', $contents, false, false);
- }
-
- return $path;
- }
-
- protected static function _getBackupClassName($className)
- {
- $parts = explode('_', $className);
- $prefix = array_shift($parts);
- $suffix = implode('_', $parts);
- return $prefix . '_DevHelper_Generated_' . $suffix;
- }
-
- public static function fileGetContents($path)
- {
- if (is_readable($path)) {
- $contents = file_get_contents($path);
-
- return $contents;
- } else {
- return false;
- }
- }
-
- public static function filePutContents($path, $contents)
- {
- $dir = dirname($path);
- XenForo_Helper_File::createDirectory($dir);
- if (!is_dir($dir) OR !is_writable($dir)) {
- return false;
- }
-
- if (file_put_contents($path, $contents) > 0) {
- XenForo_Helper_File::makeWritableByFtpUser($path);
- return true;
- }
-
- return false;
- }
-
- public static function generateHashesFile(
- array $addOn,
- DevHelper_Config_Base $config,
- array $directories,
- $exportPath,
- $rootPath
- ) {
- $hashes = array();
- $exportPath = rtrim($exportPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
- $rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
-
- $excludes = $config->getExportExcludes();
-
- foreach ($directories as $key => $directory) {
- $directory = preg_replace('#^' . preg_quote($rootPath, '#') . '#', $exportPath, $directory);
- $directoryHashes = XenForo_Helper_Hash::hashDirectory($directory, array(
- '.php',
- '.js'
- ));
-
- foreach ($directoryHashes as $filePath => $hash) {
- if (strpos($filePath, 'DevHelper') === false AND strpos($filePath, 'FileSums') === false) {
- $relative = preg_replace('#^' . preg_quote($exportPath, '#') . '#', '', $filePath);
-
- $excluded = false;
- foreach ($excludes as $exclude) {
- if (strpos($relative, $exclude) === 0) {
- $excluded = true;
- break;
- }
- }
- if ($excluded) {
- continue;
- }
-
- $hashes[$relative] = $hash;
- }
- }
- }
-
- $fileSumsClassName = self::getClassName($addOn['addon_id'], 'FileSums', $config);
- $hashesExported = self::varExport($hashes, 2);
- $fileSumsContents = "getClassPrefix();
-
- $libraryPath = self::getLibraryPath($config);
- $rootPath = dirname($libraryPath);
-
- $list['library'] = self::getAddOnIdPath($config);
- if (empty($list['library'])) {
- throw new XenForo_Exception(sprintf('`library` not found for %s', $addOn['addon_id']));
- }
-
- $jsPath = sprintf('%s/js/%s', $rootPath, str_replace('_', DIRECTORY_SEPARATOR, $classPrefix));
- if (is_dir($jsPath)) {
- $list['js'] = $jsPath;
- }
-
- $stylesDefaultPath = sprintf(
- '%s/styles/default/%s',
- $rootPath,
- str_replace('_', DIRECTORY_SEPARATOR, $classPrefix)
- );
- if (is_dir($stylesDefaultPath)) {
- $list['styles_default'] = $stylesDefaultPath;
- }
-
- $exportIncludes = $config->getExportIncludes();
- foreach ($exportIncludes as $exportInclude) {
- $exportIncludePath = sprintf('%s/%s', $rootPath, $exportInclude);
-
- if (file_exists($exportIncludePath)) {
- $list[$exportInclude] = $exportIncludePath;
- }
- }
-
- // save add-on XML
- $xmlPath = self::getAddOnXmlPath($addOn, null, $config);
-
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = XenForo_Model::create('XenForo_Model_AddOn');
- $addOnModel->getAddOnXml($addOn)->save($xmlPath);
- echo "Exported $xmlPath ($addOn[version_string]/$addOn[version_id])\n";
- DevHelper_Helper_Phrase::parseXmlForPhraseTracking($xmlPath);
-
- $exportAddOns = $config->getExportAddOns();
- foreach ($exportAddOns as $exportAddOnId) {
- $exportAddOn = $addOnModel->getAddOnById($exportAddOnId);
- if (empty($exportAddOn)) {
- die(sprintf("Could not find add-on %s\n", $exportAddOnId));
- }
-
- $exportAddOnPath = self::getAddOnXmlPath($addOn, $exportAddOn, $config);
- $addOnModel->getAddOnXml($exportAddOn)->save($exportAddOnPath);
- echo "Exported $exportAddOnPath ($exportAddOn[version_string]/$exportAddOn[version_id])\n";
- }
-
- $exportStyles = $config->getExportStyles();
- if (!empty($exportStyles)) {
- /** @var XenForo_Model_Style $styleModel */
- $styleModel = $addOnModel->getModelFromCache('XenForo_Model_Style');
- $styles = $styleModel->getAllStyles();
- $exportedStyleCount = 0;
-
- foreach ($styles as $style) {
- if (in_array($style['title'], $exportStyles, true)) {
- $stylePath = self::getStyleXmlPath($addOn, $style, $config);
- $styleModel->getStyleXml($style)->save($stylePath);
- echo "Exported $stylePath\n";
- $exportedStyleCount++;
- }
- }
-
- if ($exportedStyleCount < count($exportStyles)) {
- die("Not all export styles could be found...\n");
- }
- }
-
- // check for file_health_check event listener
- /** @var XenForo_Model_CodeEvent $codeEventModel */
- $codeEventModel = XenForo_Model::create('XenForo_Model_CodeEvent');
- $addOnEventListeners = $codeEventModel->getEventListenersByAddOn($addOn['addon_id']);
- $fileHealthCheckFound = false;
- foreach ($addOnEventListeners as $addOnEventListener) {
- if ($addOnEventListener['event_id'] === 'file_health_check') {
- $fileHealthCheckFound = true;
- }
-
- if (!is_callable(array($addOnEventListener['callback_class'], $addOnEventListener['callback_method']))) {
- die(sprintf(
- "Callback is not callable %s::%s\n",
- $addOnEventListener['callback_class'],
- $addOnEventListener['callback_method']
- ));
- }
- }
- if (!$fileHealthCheckFound) {
- // try to generate the file health check event listener ourselves
- if (DevHelper_Generator_Code_Listener::generateFileHealthCheck($addOn, $config)) {
- $fileHealthCheckFound = true;
- }
- }
- if (!$fileHealthCheckFound) {
- die("No `file_health_check` event listener found.\n");
- }
-
- if (strpos($exportPath, 'upload') === false) {
- $exportPath .= '/upload';
- }
- XenForo_Helper_File::createDirectory($exportPath /*, true */);
- $exportPath = realpath($exportPath);
- $options = array(
- 'extensions' => array(
- 'php',
- 'inc',
- 'txt',
- 'xml',
- 'htm',
- 'html',
- 'js',
- 'css',
- 'jpg',
- 'jpeg',
- 'png',
- 'gif',
- 'swf',
- 'crt',
- 'pem',
- 'eot',
- 'svg',
- 'ttf',
- 'woff',
- 'woff2',
- 'otf',
- 'md',
- ),
- 'filenames_lowercase' => array(
- 'license',
- 'readme',
- 'copyright',
- '.htaccess',
- 'changelog',
- 'composer.json',
- 'readme.rdoc',
- 'version',
- ),
- 'force' => true, // always force add top level export entries
- 'addon_id' => $addOn['addon_id'],
-
- 'excludes' => array(),
- 'excludeRegExs' => array(),
- );
-
- $excludes = $config->getExportExcludes();
- foreach ($excludes as $exclude) {
- if (preg_match('/^#.+#$/', $exclude)) {
- $options['excludeRegExs'][] = $exclude;
- } else {
- $options['excludes'][] = $exclude;
- }
- }
-
- // run phpcbf by default if available
- // append phpcs=1 in file-export url to run phpcs instead
- if (!empty($_REQUEST['phpcbf'])) {
- $codingStandardBinary = exec('which phpcbf');
- } else {
- $codingStandardBinary = exec('which phpcs');
- if (!empty($codingStandardBinary)) {
- $codingStandardBinary .= ' -s';
- }
- }
- $phpcsOutput = array();
- foreach ($list as $type => $entry) {
- if (!empty($codingStandardBinary)) {
- exec(sprintf(
- '%1$s --standard=%3$s %2$s',
- $codingStandardBinary,
- escapeshellarg($entry),
- escapeshellarg(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpcs.xml')
- ), $phpcsOutput);
- }
-
- self::_fileExport($entry, $exportPath, $rootPath, array_merge($options, array('type' => $type)));
- }
-
- $hashesFilePath = self::generateHashesFile($addOn, $config, $list, $exportPath, $rootPath);
- self::_fileExport($hashesFilePath, $exportPath, $rootPath, $options);
-
- // copy one xml copy to the export directory directory
- $xmlCopyPath = sprintf('%s/%s', dirname($exportPath), basename($xmlPath));
- if (@copy($xmlPath, $xmlCopyPath)) {
- echo "Copied $xmlPath -> $xmlCopyPath\n";
- } else {
- echo "Can't cp $xmlPath -> $xmlCopyPath\n";
- }
-
- echo(implode("\n", $phpcsOutput));
- }
-
- protected static function _fileExport($entry, &$exportPath, &$rootPath, $options)
- {
- if (empty($entry)) {
- return;
- }
-
- $relativePath = trim(str_replace($rootPath, '', $entry), '/');
-
- if (empty($options['force'])) {
- if (in_array($relativePath, $options['excludes'])) {
- echo "Excluded $relativePath \n";
- return;
- }
-
- foreach ($options['excludeRegExs'] as $excludeRegEx) {
- if (preg_match($excludeRegEx, $relativePath)) {
- echo "RegExcluded $relativePath \n";
- return;
- }
- }
-
- $baseName = basename($relativePath);
- switch ($baseName) {
- case 'FileSums.php':
- case 'upload':
- return;
- }
- }
-
- if (is_dir($entry)) {
- echo "Browsing $relativePath \n";
-
- $children = array();
-
- $dh = opendir($entry);
- while ($child = readdir($dh)) {
- if (substr($child, 0, 1) === '.') {
- // ignore . (current directory)
- // ignore .. (parent directory)
- // ignore hidden files (dot files/directories)
- continue;
- }
-
- $children[] = $child;
- }
-
- foreach ($children as $child) {
- if (!empty($options['force'])) {
- $options['force'] = false;
- }
- // reset `force` option for children
- self::_fileExport($entry . '/' . $child, $exportPath, $rootPath, $options);
- }
- } elseif (is_file($entry)) {
- $ext = XenForo_Helper_File::getFileExtension($entry);
- if (!empty($options['force'])
- || (in_array($ext, $options['extensions'])
- && strpos(basename($entry), '.') !== 0)
- || in_array(strtolower(basename($entry)), $options['filenames_lowercase'])
- ) {
- if ($options['addon_id'] == 'devHelper') {
- $isDevHelper = (strpos($entry, 'DevHelper/DevHelper') !== false);
- } else {
- $isDevHelper = (strpos($entry, 'DevHelper') !== false);
- }
-
- if (!$isDevHelper) {
- $entryExportPath = $exportPath . '/' . $relativePath;
- $entryExportPathOriginal = $entryExportPath;
- $contents = null;
-
- switch ($ext) {
- case 'js':
- if ($options['type'] === 'js'
- && preg_match('#\.min\.js$#', $entry)
- ) {
- $fullEntry = sprintf(
- '%s/full/%s',
- dirname($entry),
- preg_replace('#\.min\.js$#', '.js', basename($entry))
- );
- if (self::fileGetContents($fullEntry)) {
- $contents = '';
- }
- }
- break;
- case 'php':
- DevHelper_Helper_ShippableHelper::checkForUpdate($entry);
- break;
- }
-
- if ($contents === null) {
- $contents = self::fileGetContents($entry);
- }
-
- if (!empty($contents)) {
- switch ($ext) {
- case 'js':
- if ($options['type'] === 'js'
- && basename(dirname($entry)) === 'full'
- && !preg_match('#\.min\.js$#', $entry)
- ) {
- $entryExportPath = sprintf(
- '%s/%s',
- dirname(dirname($entryExportPathOriginal)),
- preg_replace('#\.js$#', '.min.js', basename($entry))
- );
- $minPath = DevHelper_Helper_Js::minifyPath($entry);
- $contents = self::fileGetContents($minPath);
- }
- break;
- case 'php':
- DevHelper_Helper_Phrase::parsePhpForPhraseTracking($relativePath, $contents);
- DevHelper_Helper_Xfcp::parsePhpForXfcpClass($relativePath, $contents);
- break;
- }
-
- $result = self::writeFile($entryExportPath, $contents, false, false);
- } else {
- $result = 'empty';
- }
-
- if ($result === true) {
- $result = 'OK';
- if ($entryExportPath !== $entryExportPathOriginal) {
- $result = preg_replace(
- '#^' . preg_quote($exportPath, '#') . '/#',
- '',
- $entryExportPath
- );
- $result = '-> ' . $result;
- }
-
- echo "Exporting {$relativePath} $result\n";
- } else {
- echo "Exporting {$relativePath} $result \n";
- }
- }
- }
- }
- }
-
- public static function varExport($var, $level = 1, $linePrefix = " ", $noKey = false)
- {
- $output = '';
-
- if (is_array($var)) {
- $arrayVars = array();
- $multiLine = false;
- $keyValueLength = 0;
- $allKeysAreInt = true;
- foreach ($var as $key => $value) {
- $arrayVars[$key] = self::varExport($value, $level + 1, $linePrefix);
- if (is_array($value) AND count($value) > 1) {
- $multiLine = true;
- }
- if (strpos($arrayVars[$key], "\n") !== false) {
- $multiLine = true;
- }
-
- $keyValueLength += strlen($key);
- if (is_array($value)) {
- $keyValueLength += strlen(var_export($value, true));
- } else {
- $keyValueLength += strlen($value);
- }
-
- if (!is_int($key)) {
- $allKeysAreInt = false;
- }
- }
- if ($keyValueLength > 100) {
- $multiLine = true;
- }
- if ($allKeysAreInt) {
- $noKey = true;
- }
-
- $output .= 'array(';
- $first = true;
- foreach ($arrayVars as $key => $str) {
- if ($multiLine) {
- $output .= "\n" . str_repeat($linePrefix, $level + 1);
- } else {
- if (!$first) {
- $output .= ', ';
- }
- }
-
- if (empty($noKey)) {
- $output .= var_export($key, true) . ' => ';
- }
-
- $output .= $str;
-
- if ($multiLine) {
- $output .= ',';
- }
-
- $first = false;
- }
-
- if ($multiLine) {
- $output .= "\n" . str_repeat($linePrefix, $level);
- }
-
- $output .= ')';
- } elseif (is_object($var) && $var instanceof _DevHelper_Generator_File_Constant) {
- $output .= strval($var);
- } else {
- $tmp = var_export($var, true);
- if (strpos($tmp, "\n") !== false) {
- $tmp = str_replace("\n", "\n" . str_repeat($linePrefix, $level), $tmp);
- }
-
- $output .= $tmp;
- }
-
- return $output;
- }
-
- public static function varExportConstant($str)
- {
- return new _DevHelper_Generator_File_Constant($str);
- }
-
- public static function varExportConstantFromArray($value, array $constants)
- {
- foreach ($constants as $constant) {
- if ($value === constant($constant)) {
- return self::varExportConstant($constant);
- }
- }
-
- return $value;
- }
-}
-
-class _DevHelper_Generator_File_Constant
-{
- protected $_str = '';
-
- public function __construct($str)
- {
- $this->_str = $str;
- }
-
- public function __toString()
- {
- return $this->_str;
- }
-}
diff --git a/library/DevHelper/Generator/Phrase.php b/library/DevHelper/Generator/Phrase.php
deleted file mode 100755
index a9e6ff4..0000000
--- a/library/DevHelper/Generator/Phrase.php
+++ /dev/null
@@ -1,64 +0,0 @@
-bulkSet(array(
- 'title' => $title,
- 'phrase_text' => $phraseText,
- 'language_id' => 0,
- 'global_cache' => 0,
- 'addon_id' => $addOn['addon_id'],
- ));
- $writer->updateVersionId();
- $writer->save();
-
- return true;
- }
-
- public static function generatePhraseAutoCamelCaseStyle(
- array $addOn,
- DevHelper_Config_Base $config,
- array $dataClass,
- $dashText
- ) {
- $camelCase = ucwords(str_replace('_', ' ', $dashText));
- $title = self::getPhraseName($addOn, $config, $dataClass, $dashText);
- self::generatePhrase($addOn, $title, $camelCase);
-
- return $title;
- }
-
- public static function checkPhraseExists($title)
- {
- $info = self::_getPhraseModel()->getPhrasesInLanguageByTitles(array($title), 0);
-
- return !empty($info);
- }
-
- /**
- * @return XenForo_Model_Phrase
- */
- protected static function _getPhraseModel()
- {
- /** @var XenForo_Model_Phrase $model */
- static $model = null;
-
- if ($model === null) {
- $model = XenForo_Model::create('XenForo_Model_Phrase');
- }
-
- return $model;
- }
-}
diff --git a/library/DevHelper/Generator/Template.php b/library/DevHelper/Generator/Template.php
deleted file mode 100755
index 0a512bc..0000000
--- a/library/DevHelper/Generator/Template.php
+++ /dev/null
@@ -1,70 +0,0 @@
-keyPropertiesByName($propertyModel->getEffectiveStylePropertiesInStyle(-1));
- $propertyChanges = $propertyModel->translateEditorPropertiesToArray($template, $template, $properties);
-
- $writer = XenForo_DataWriter::create('XenForo_DataWriter_AdminTemplate');
- $writer->bulkSet(array(
- 'title' => $title,
- 'template' => $template,
- 'addon_id' => $addOn['addon_id'],
- ));
-
- try {
- $writer->save();
- } catch (Exception $ex) {
- throw new XenForo_Exception("Exception creating template $title: " .
- $ex->getMessage() . '' . htmlentities($template) . ' ');
- }
-
- $propertyModel->saveStylePropertiesInStyleFromTemplate(-1, $propertyChanges, $properties);
-
- return true;
- }
-
- public static function checkAdminTemplateExists($title)
- {
- $info = self::_getAdminTemplateModel()->getAdminTemplateByTitle($title);
-
- return !empty($info);
- }
-
- /**
- * @return XenForo_Model_AdminTemplate
- */
- protected static function _getAdminTemplateModel()
- {
- /** @var XenForo_Model_AdminTemplate $model */
- static $model = null;
-
- if ($model === null) {
- $model = XenForo_Model::create('XenForo_Model_AdminTemplate');
- }
-
- return $model;
- }
-
- /**
- * @return XenForo_Model_StyleProperty
- */
- protected static function _getStylePropertyModel()
- {
- /** @noinspection PhpIncompatibleReturnTypeInspection */
- return self::_getAdminTemplateModel()->getModelFromCache('XenForo_Model_StyleProperty');
- }
-}
diff --git a/library/DevHelper/Generator/phpcs.xml b/library/DevHelper/Generator/phpcs.xml
deleted file mode 100644
index 23c95fb..0000000
--- a/library/DevHelper/Generator/phpcs.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- */CodeMirror/
- */DevHelper/Generated/
- */Lib/
- */ShippableHelper/
- */tests/
- *.min.js
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/library/DevHelper/Helper/File.php b/library/DevHelper/Helper/File.php
deleted file mode 100644
index 6fdb444..0000000
--- a/library/DevHelper/Helper/File.php
+++ /dev/null
@@ -1,34 +0,0 @@
- static::FIND_XML_IF_PATHS_COUNT_LESS_THAN) {
- return '';
- }
-
- $subPaths = array();
- foreach ($paths as $path) {
- if (!is_dir($path)) {
- continue;
- }
-
- $contentPaths = glob(sprintf('%s/*', rtrim($path, '/')));
- foreach ($contentPaths as $contentPath) {
- if (is_dir($contentPath)) {
- $subPaths[] = $contentPath . '/';
- } else {
- $ext = XenForo_Helper_File::getFileExtension($contentPath);
- if ($ext === 'xml') {
- return $contentPath;
- }
- }
- }
- }
-
- return static::findXml($subPaths);
- }
-}
diff --git a/library/DevHelper/Helper/Js.php b/library/DevHelper/Helper/Js.php
deleted file mode 100644
index 4d985f0..0000000
--- a/library/DevHelper/Helper/Js.php
+++ /dev/null
@@ -1,83 +0,0 @@
-&1',
- escapeshellarg($fullPath),
- escapeshellarg($minPath),
- escapeshellarg(sprintf(
- 'root=full,base="%1$s",url=%2$s.map',
- dirname($fullPath),
- basename($minPath)
- ))
- );
-
- exec($command, $uglifyOutput, $uglifyResult);
-
- if (!file_exists($minPath) || $uglifyResult !== 0) {
- echo(sprintf("%s: %s\n%s -> %d \n", __METHOD__, $fullPath, $command, $uglifyResult));
- var_export($uglifyOutput);
- exit($uglifyResult);
- }
-
- return $minPath;
- }
-
- public static function processJsFiles(array $jsFiles)
- {
- foreach ($jsFiles as $path) {
- if (strpos($path, 'js') !== 0) {
- // ignore non js files
- continue;
- }
-
- if (strpos($path, 'js/xenforo/') === 0) {
- // ignore xenforo files
- continue;
- }
-
- if (!preg_match('#\.min\.js$#', $path)) {
- // ignore non .min.js files
- continue;
- }
-
- /** @var XenForo_Application $application */
- $application = XenForo_Application::getInstance();
- $fullPath = sprintf(
- '%s/%s/full/%s',
- $application->getRootDir(),
- dirname($path),
- preg_replace('#\.min\.js$#', '.js', basename($path))
- );
- if (!empty($_SERVER['DEVHELPER_ROUTER_PHP'])) {
- $fullPath = DevHelper_Router::locate($fullPath);
- }
-
- if (file_exists($fullPath)) {
- static::minifyPath($fullPath);
- }
- }
- }
-}
diff --git a/library/DevHelper/Helper/Php.php b/library/DevHelper/Helper/Php.php
deleted file mode 100644
index bd2b40e..0000000
--- a/library/DevHelper/Helper/Php.php
+++ /dev/null
@@ -1,107 +0,0 @@
-= $phpLength) {
- // unclosed string
- return false;
- }
- $next = substr($php, $innerOffset, 1);
- } elseif ($next === $operator) {
- // found another instance of the operator, end of string
- $offset = $innerOffset;
- return $string;
- }
-
- $string .= $next;
- $innerOffset++;
- if ($innerOffset >= $phpLength) {
- // unclosed string
- return false;
- }
- }
- break;
- }
-
- return false;
- }
-
- public static function extractMethods($php)
- {
- $methods = array();
-
- $offset = 0;
- while (true) {
- if (preg_match(
- '#(public|protected|private|static|\s)*'
- . 'function\s+(?[a-zA-Z0-9_]+)\s*\([^\)]*\)\s*{#',
- $php,
- $matches,
- PREG_OFFSET_CAPTURE,
- $offset
- )) {
- $methods[] = $matches['method'][0];
- $offset = $matches[0][1] + strlen($matches[0][0]);
- } else {
- break;
- }
- }
-
- return $methods;
- }
-
- public static function appendMethod($php, $methodCode)
- {
- $lines = explode("\n", $php);
- $backUp = array();
-
- while (true) {
- $lastLine = array_pop($lines);
- $pos = strrpos($lastLine, '}');
- if ($pos === false) {
- $backUp[] = $lastLine;
- continue;
- }
-
- $prev = substr($lastLine, 0, $pos);
- $lines[] = $prev;
-
- $indent = ' ';
- if (preg_match('#^(?\s+)([^\s]|$)#', $prev, $matches)) {
- $indent .= $matches['indent'];
- }
-
- $methodCodeLines = explode("\n", $methodCode);
- foreach ($methodCodeLines as $methodCodeLine) {
- $lines[] = $indent . $methodCodeLine;
- }
-
- $lines[] = substr($lastLine, $pos);
- break;
- }
-
- $contents = implode("\n", $lines);
- if (!empty($backUp)) {
- $contents .= "\n" . implode("\n", array_reverse($backUp));
- }
- return $contents;
- }
-}
diff --git a/library/DevHelper/Helper/Phrase.php b/library/DevHelper/Helper/Phrase.php
deleted file mode 100644
index d131ba4..0000000
--- a/library/DevHelper/Helper/Phrase.php
+++ /dev/null
@@ -1,149 +0,0 @@
-{$phrase['title']}\n";
- } else {
- $usedPhraseTitles[] = $phrase['title'];
- }
- }
-
- foreach ($foundPhraseTitles as $phraseTitle) {
- if (!in_array($phraseTitle, $usedPhraseTitles)) {
- $foundButUnusedPhraseTitles[] = $phraseTitle;
- }
- }
- if (!empty($foundButUnusedPhraseTitles)) {
- $foundButUnusedPhrases = $phraseModel->getPhrasesInLanguageByTitles($foundButUnusedPhraseTitles);
- foreach ($foundButUnusedPhraseTitles as $phraseTitle) {
- $good = true;
-
- if (empty($foundButUnusedPhrases[$phraseTitle])) {
- echo "Phrase not found: {$phraseTitle} \n";
- $good = false;
- }
-
- if ($good) {
- $foundButUnusedPhrase = $foundButUnusedPhrases[$phraseTitle];
- if (!empty($foundButUnusedPhrase['addon_id']) AND $foundButUnusedPhrase['addon_id'] !== 'XenForo') {
- echo "Phrase from another add-on: {$phraseTitle} ({$foundButUnusedPhrase['addon_id']})\n";
- $good = false;
- }
- }
-
- if (!$good) {
- $phraseUsages = self::getPhraseUsages($phraseTitle);
- foreach ($phraseUsages as $phraseUsagePath => $phraseUsageLine) {
- echo " Used in {$phraseUsagePath}:{$phraseUsageLine} \n";
- }
- }
- }
- }
- }
-
- public static function getFoundPhraseTitles()
- {
- return array_keys(self::$_foundPhraseTitles);
- }
-
- public static function getPhraseUsages($phraseTitle)
- {
- if (isset(self::$_foundPhraseTitles[$phraseTitle])) {
- return self::$_foundPhraseTitles[$phraseTitle];
- } else {
- return array();
- }
- }
-
- public static function parsePhpForPhraseTracking($path, $contents)
- {
- if (self::$_lookingForPhraseTitles == false) {
- return;
- }
-
- if (strpos($path, 'library/DevHelper/') === 0) {
- return;
- }
-
- $offset = 0;
- $newXenForoPhrase = 'new XenForo_Phrase(';
- while (true) {
- $strPos = strpos($contents, $newXenForoPhrase, $offset);
-
- if ($strPos !== false) {
- $offset = $strPos + strlen($newXenForoPhrase);
- $phraseTitle = DevHelper_Helper_Php::extractString($contents, $offset);
-
- if (is_string($phraseTitle)) {
- self::$_foundPhraseTitles[$phraseTitle][$path] = substr_count(substr($contents, 0, $offset), "\n");
- } else {
- continue;
- }
- } else {
- break;
- }
- }
- }
-
- public static function parseXmlForPhraseTracking($path)
- {
- if (self::$_lookingForPhraseTitles == false) {
- return;
- }
-
- $contents = file_get_contents($path);
-
- $offset = 0;
- while (true) {
- if (preg_match(
- '/{xen:phrase\s+(?[^,}]+)(,|})/',
- $contents,
- $matches,
- PREG_OFFSET_CAPTURE,
- $offset
- )) {
- $phraseTitle = $matches['title'][0];
- self::$_foundPhraseTitles[$phraseTitle][$path] = substr_count(substr($contents, 0, $offset), "\n");
- $offset = $matches[0][1] + strlen($matches[0][0]);
- } else {
- break;
- }
- }
- }
-}
diff --git a/library/DevHelper/Helper/ShippableHelper.php b/library/DevHelper/Helper/ShippableHelper.php
deleted file mode 100644
index 2d87e64..0000000
--- a/library/DevHelper/Helper/ShippableHelper.php
+++ /dev/null
@@ -1,85 +0,0 @@
-\d+)\s.+?\*/#s',
- $contents,
- $matches
- )) {
- return intval($matches['version']);
- } else {
- return false;
- }
- }
-
- public static function update($targetClass, $targetPath, $sourceClass, $sourcesContents)
- {
- $targetContents = str_replace($sourceClass, $targetClass, $sourcesContents);
-
- $php = '[^\s]+_ShippableHelper_[^\s]+)\s#', $contents, $matches)) {
- class_exists($matches['class']);
- }
- }
- }
-}
diff --git a/library/DevHelper/Helper/ShippableHelper/Crypt.php b/library/DevHelper/Helper/ShippableHelper/Crypt.php
deleted file mode 100644
index bf7e9d5..0000000
--- a/library/DevHelper/Helper/ShippableHelper/Crypt.php
+++ /dev/null
@@ -1,153 +0,0 @@
-get('globalSalt');
- }
-
- switch ($algo) {
- case self::ALGO_AES_128:
- return self::_aes128_encrypt($data, $key);
- default:
- case self::ALGO_AES_256:
- return self::_aes256_encrypt($data, $key);
- }
- }
-
- public static function decrypt($data, $key = null, $algo = null)
- {
- if ($key === null) {
- $key = XenForo_Application::getConfig()->get('globalSalt');
- }
-
- if ($algo === null) {
- if (substr($data, 0, strlen(self::ALGO_AES_256)) === self::ALGO_AES_256) {
- $algo = self::ALGO_AES_256;
- } else {
- $algo = self::ALGO_AES_128;
- }
- }
-
- switch ($algo) {
- case self::ALGO_AES_128:
- return self::_aes128_decrypt($data, $key);
- default:
- case self::ALGO_AES_256:
- return self::_aes256_decrypt($data, $key);
- }
- }
-
- /**
- * Legacy AES 128 encryption.
- * Supports both OpenSSL and mcrypt.
- * Warning: This method is insecure and potentially dangerous, should be avoided for new application.
- *
- * @param string $data
- * @param string $key
- * @return string
- */
- protected static function _aes128_encrypt($data, $key)
- {
- if (function_exists('openssl_encrypt')) {
- $key = md5($key, true);
- return openssl_encrypt($data, self::OPENSSL_METHOD_AES128, $key, OPENSSL_RAW_DATA);
- }
-
- if (function_exists('mcrypt_encrypt')) {
- $key = md5($key, true);
- $padding = 16 - (strlen($data) % 16);
- $data .= str_repeat(chr($padding), $padding);
- /** @noinspection PhpDeprecationInspection */
- return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB);
- }
-
- return $data;
- }
-
- /**
- * Legacy AES 128 decryption.
- * Supports both OpenSSL and mcrypt.
- * Warning: This method is insecure and potentially dangerous, should be avoided for new application.
- *
- * @param string $data
- * @param string $key
- * @return string
- */
- protected static function _aes128_decrypt($data, $key)
- {
- if (function_exists('openssl_decrypt')) {
- $key = md5($key, true);
- return openssl_decrypt($data, self::OPENSSL_METHOD_AES128, $key, OPENSSL_RAW_DATA);
- }
-
- if (function_exists('mcrypt_decrypt')) {
- $key = md5($key, true);
- /** @noinspection PhpDeprecationInspection */
- $data = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB);
- $padding = ord($data[strlen($data) - 1]);
- return substr($data, 0, -$padding);
- }
-
- return $data;
- }
-
- /**
- * Secure AES 256 encryption.
- * This method only supports OpenSSL (unlike the AES 128 variant which also supports mcrypt).
- *
- * @param string $data
- * @param string $key
- * @return string
- */
- protected static function _aes256_encrypt($data, $key)
- {
- if (function_exists('mb_substr') && function_exists('openssl_encrypt')) {
- $ivLength = openssl_cipher_iv_length(self::OPENSSL_METHOD_AES256);
- $iv = openssl_random_pseudo_bytes($ivLength);
- $encrypted = openssl_encrypt($data, self::OPENSSL_METHOD_AES256, $key, OPENSSL_RAW_DATA, $iv);
-
- return self::ALGO_AES_256 . $iv . $encrypted;
- }
-
- return $data;
- }
-
- /**
- * Secure AES 256 decryption.
- * This method only supports OpenSSL (unlike the AES 128 variant which also supports mcrypt).
- *
- * @param string $data
- * @param string $key
- * @return string
- */
- protected static function _aes256_decrypt($data, $key)
- {
- if (function_exists('mb_substr') && function_exists('openssl_encrypt')) {
- $prefixLength = mb_strlen(self::ALGO_AES_256, '8bit');
- $prefix = mb_substr($data, 0, $prefixLength);
- if ($prefix === self::ALGO_AES_256) {
- $ivLength = openssl_cipher_iv_length(self::OPENSSL_METHOD_AES256);
- $iv = mb_substr($data, $prefixLength, $ivLength, '8bit');
- $encrypted = mb_substr($data, $prefixLength + $ivLength, null, '8bit');
-
- return openssl_decrypt($encrypted, self::OPENSSL_METHOD_AES256, $key, OPENSSL_RAW_DATA, $iv);
- }
- }
-
- return $data;
- }
-
-}
diff --git a/library/DevHelper/Helper/ShippableHelper/DateTime.php b/library/DevHelper/Helper/ShippableHelper/DateTime.php
deleted file mode 100644
index 433aa65..0000000
--- a/library/DevHelper/Helper/ShippableHelper/DateTime.php
+++ /dev/null
@@ -1,99 +0,0 @@
- 0 &&
- $city['offset'] == $offset
- ) {
- $zone = $city['timezone_id'];
- break;
- }
- }
-
- if ($zone !== false) {
- break;
- }
- }
- }
-
- return $zone;
- }
-
- /**
- * Get Unix timestamp in GMT for inputted date values.
- *
- * @param int|array $year in user timezone
- * @param int $month in user timezone
- * @param int $day in user timezone
- * @param int $hour in user timezone
- * @param int $minute in user timezone
- * @param int $second in user timezone
- * @param int|null $offset custom offset to override user's
- *
- * @return int timestamp in GMT
- */
- public static function gmmktime($year, $month = 0, $day = 0, $hour = 0, $minute = 0, $second = 0, $offset = null)
- {
- if (is_array($year)) {
- $args = func_get_args();
- if (count($args) !== 1) {
- return 0;
- }
-
- $values = $year;
- $year = 0;
- foreach (array('year', 'month', 'day', 'hour', 'minute', 'second', 'offset') as $key) {
- if (isset($values[$key])
- && is_int($values[$key])
- ) {
- $$key = intval($values[$key]);
- }
- }
- }
-
- $timestamp = gmmktime($hour, $minute, $second, $month, $day, $year);
-
- if ($offset === null) {
- $offset = XenForo_Locale::getTimeZoneOffset();
- }
-
- return $timestamp - $offset;
- }
-
- /**
- * Get timezone offset by string or object
- *
- * @param string|DateTimeZone $timeZoneString String time zone to override user timezone
- * @return int
- */
- public static function getTimeZoneOffset($timeZoneString)
- {
- $dt = new DateTime('@' . XenForo_Application::$time);
-
- if ($timeZoneString instanceof DateTimeZone) {
- $timeZone = $timeZoneString;
- } else {
- $timeZone = new DateTimeZone($timeZoneString);
- }
- $dt->setTimezone($timeZone);
-
- return $dt->getOffset();
- }
-
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/Html.php b/library/DevHelper/Helper/ShippableHelper/Html.php
deleted file mode 100644
index 5f65eb9..0000000
--- a/library/DevHelper/Helper/ShippableHelper/Html.php
+++ /dev/null
@@ -1,399 +0,0 @@
- 'prbreak',
- 'messageKey' => 'message',
- ), $options);
-
- $previewFound = false;
- $tagOpen = '';
- $tagClose = '';
-
- if (!empty($options['previewBreakBbCode'])) {
- $textRef =& $message[$options['messageKey']];
- $tagOpen = '[' . $options['previewBreakBbCode'] . ']';
- $posOpen = stripos($textRef, $tagOpen);
- if ($posOpen !== false) {
- $tagClose = '[/' . $options['previewBreakBbCode'] . ']';
- $posClose = stripos($textRef, $tagClose, $posOpen);
- if ($posClose !== false) {
- $previewFound = true;
- $previewTextOffset = $posOpen + strlen($tagOpen);
- $previewTextLength = $posClose - $posOpen - strlen($tagOpen);
-
- if ($previewTextLength > 0) {
- $textRef = substr($textRef, $previewTextOffset, $previewTextLength);
-
- } else {
- $textRef = substr($textRef, 0, $posOpen);
- }
- }
- }
- }
-
- $messageHtml = XenForo_ViewPublic_Helper_Message::getBbCodeWrapper(
- $message, $parser, $options);
-
- if ($previewFound) {
- $messageHtml = sprintf('%s%s%s', $tagOpen, $messageHtml, $tagClose);
- }
-
- return $messageHtml;
- }
-
- /**
- * @param string $string
- * @param int $maxLength
- * @param array $options
- *
- * @return string
- */
- public static function snippet($string, $maxLength = 0, array $options = array())
- {
- $options = array_merge(array(
- 'context' => '',
- 'ellipsis' => '…',
- 'fromStart' => true,
- 'previewBreakBbCode' => 'prbreak',
- 'processImage' => true,
- 'processLink' => true,
- 'processFrame' => true,
- 'processScript' => true,
- 'replacements' => array(),
- 'stripImage' => false,
- 'stripLink' => false,
- 'stripFrame' => false,
- 'stripScript' => false,
- 'stripSpacing' => true,
-
- // WARNING: options below are for internal usage only
- '_isPreview' => false,
- ), $options);
- if (!isset($options['maxLength'])) {
- $options['maxLength'] = $maxLength;
- }
-
- self::snippetPreProcess($string, $options);
- if (!empty($options['_isPreview'])) {
- $snippet = $string;
- } else {
- $snippet = self::snippetCallHelper($string, $options);
- self::snippetFixBrokenHtml($snippet, $options);
- }
-
- if (!empty($options['context'])) {
- self::snippetOnContext($snippet, $options['context']);
- }
-
- if ($snippet === '') {
- $plainTextString = utf8_trim(strip_tags($string));
- if ($plainTextString !== '') {
- $snippet = self::snippetCallHelper($plainTextString, $options);
- }
- }
-
- self::snippetPostProcess($snippet, $options);
-
- return $snippet;
- }
-
- public static function snippetPreProcess(&$string, array &$options)
- {
- if (!empty($options['previewBreakBbCode'])
- && preg_match(sprintf('#\[%1$s\](?<' . 'preview>.*)\[/%1$s\]#si',
- preg_quote($options['previewBreakBbCode'], '#')),
- $string, $matches, PREG_OFFSET_CAPTURE)
- ) {
- // preview break bbcode found
- if (!empty($matches['preview'][0])) {
- // preview text specified, use it directly
- $string = $matches['preview'][0];
- $options['maxLength'] = 0;
- $options['_isPreview'] = true;
- } else {
- // use content before the found bbcode to continue
- $string = substr($string, 0, $matches[0][1]);
- $options['maxLength'] = 0;
- }
- }
-
- $string = str_replace("\n", ' ', $string);
- $string = preg_replace('# #', "\n", $string);
- $string = str_replace('', '', $string);
-
- if (!empty($options['_isPreview'])) {
- return;
- }
-
- $replacementsRef =& $options['replacements'];
- $replacementTags = array();
- if (!!$options['processImage'] && !$options['stripImage']) {
- $replacementTags[] = 'img';
- }
- if (!!$options['processLink'] && !$options['stripLink']) {
- $replacementTags[] = 'a';
- }
- if (!!$options['processFrame'] && !$options['stripFrame']) {
- $replacementTags[] = 'iframe';
- }
- if (!!$options['processScript'] && !$options['stripScript']) {
- $replacementTags[] = 'script';
- }
- if (count($replacementTags) > 0) {
- $replacementOffset = 0;
- $replacementRegEx = sprintf('#<(%s)(\s[^>]*)?>.*?\\1>#i', implode('|', $replacementTags));
- while (true) {
- if (!preg_match($replacementRegEx, $string,
- $replacementMatches, PREG_OFFSET_CAPTURE, $replacementOffset)
- ) {
- break;
- }
- $replacement = array(
- 'original' => $replacementMatches[0][0],
- 'replacement' => sprintf(' ', count($replacementsRef)),
- );
- $replacementsRef[] = $replacement;
- $string = str_replace($replacement['original'], $replacement['replacement'], $string);
- $replacementOffset = $replacementMatches[0][1] + 1;
- }
- }
-
- if (!!$options['stripImage']) {
- $string = preg_replace('# ]+/>#', '', $string);
- }
- if (!!$options['stripLink']) {
- $string = preg_replace('#]+>(.+?) #', '$1', $string);
- }
- if (!!$options['stripFrame']) {
- $string = preg_replace('##', '', $string);
- }
- if (!!$options['stripScript']) {
- $string = preg_replace('##', '', $string);
- }
- if (!!$options['stripSpacing']) {
- $string = preg_replace('#(\n\s*)+#', "\n", $string);
- }
- }
-
- public static function snippetCallHelper($string, array &$options)
- {
- $snippet = XenForo_Template_Helper_Core::callHelper('snippet', array($string, $options['maxLength'], $options));
-
- // TODO: find better way to avoid having to call this to reset snippet
- $snippet = htmlspecialchars_decode($snippet);
- $snippet = preg_replace('#\.\.\.\z#', '', $snippet);
-
- return $snippet;
- }
-
- public static function snippetFixBrokenHtml(&$snippet, array &$options)
- {
- $offset = 0;
- $stack = array();
- while (true) {
- $startPos = utf8_strpos($snippet, '<', $offset);
- if ($startPos !== false) {
- $endPos = utf8_strpos($snippet, '>', $startPos);
- if ($endPos === false) {
- // we found a partial open tag, best to delete the whole thing
- $snippet = utf8_substr($snippet, 0, $startPos);
- break;
- }
-
- $foundLength = $endPos - $startPos - 1;
- $found = utf8_substr($snippet, $startPos + 1, $foundLength);
- $offset = $endPos;
-
- if (preg_match('#^(?/?)(?\w+)#', $found, $matches)) {
- $tag = $matches['tag'];
- $isClosing = !empty($matches['closing']);
- $isSelfClosing = (!$isClosing && (utf8_substr($found, $foundLength - 1, 1) === '/'));
-
- if ($isClosing) {
- $lastInStack = null;
- $lastInStackTag = null;
- if (count($stack) > 0) {
- $lastInStack = array_pop($stack);
- $lastInStackTag = $lastInStack['tag'];
- }
-
- if ($lastInStackTag !== $tag) {
- // found tag does not match the one in stack
- $replacement = '';
-
- // first we have to close the one in stack
- if ($lastInStackTag !== null) {
- $replacement .= sprintf('%s>', $tag);
- }
-
- // then we have to self close the found tag
- $replacement .= utf8_substr($snippet, $startPos, $endPos - $startPos - 1);
- $replacement .= '/>';
-
- // do the replacement
- $snippet = utf8_substr_replace($snippet, $replacement, $startPos, $endPos - $startPos);
- $offset = $startPos + utf8_strlen($snippet);
- }
- } elseif ($isSelfClosing) {
- // do nothing
- } else {
- // is opening tag
- $stack[] = array('tag' => $tag, 'offset' => $startPos, 'till' => $endPos);
- }
- }
- } else {
- break;
- }
- }
-
- // close any remaining tags
- while (!empty($stack)) {
- $stackItem = array_pop($stack);
- if (utf8_strlen($snippet) === $stackItem['till'] + 1) {
- // the latest tag is empty, delete it
- $snippet = utf8_substr($snippet, 0, $stackItem['offset']);
- continue;
- }
-
- self::snippetAppendEllipsis($snippet, $options);
- $snippet .= sprintf('%s>', $stackItem['tag']);
- }
- }
-
- public static function snippetAppendEllipsis(&$snippet, array &$options)
- {
- if (empty($options['ellipsis'])) {
- return;
- }
-
- if (!preg_match('#<\/?(div|iframe|img|li|ol|p|script|ul)[^>]*>\z#', $snippet)) {
- $snippet .= $options['ellipsis'];
- $options['ellipsis'] = '';
- }
- }
-
- public static function snippetOnContext(&$snippet, $context)
- {
- switch ($context) {
- case 'link':
- if (strpos($snippet, ']+)?>\s*<\/\\1>#', '', $snippet);
- }
-
- // restore replacements
- foreach ($options['replacements'] as $replacement) {
- $snippet = str_replace($replacement['replacement'], $replacement['original'], $snippet);
- }
-
- // remove invisible characters
- $snippet = utf8_trim($snippet);
-
- // restore line breaks
- $snippet = nl2br($snippet);
-
- self::snippetAppendEllipsis($snippet, $options);
-
- $snippet = utf8_trim($snippet);
- }
-
- public static function stripFont($html)
- {
- $html = preg_replace('#(<[^>]+)( style="[^"]+")([^>]*>)#', '$1$3', $html);
- $html = preg_replace('#<\/?(b|i)>#', '', $html);
-
- return $html;
- }
-
- public static function getMetaTags($html)
- {
- $tags = array();
-
- $headPos = strpos($html, '');
- if ($headPos === false) {
- return $tags;
- }
-
- $head = substr($html, 0, $headPos);
-
- $offset = 0;
- while (true) {
- if (preg_match('# ]+>#i', $head, $matches, PREG_OFFSET_CAPTURE, $offset)) {
- $tag = $matches[0][0];
- $offset = $matches[0][1] + strlen($tag);
- $name = null;
- $value = null;
-
- if (preg_match('#(name|property)="(?[^"]+)"#i', $tag, $matches)) {
- $name = $matches['name'];
- } elseif (preg_match('#itemprop="(?[^"]+)"#i', $tag, $matches)) {
- $name = $matches['itemprop'];
- } else {
- continue;
- }
-
- if (preg_match('#content="(?[^"]+)"#', $tag, $matches)) {
- $value = self::entityDecode($matches['value']);
- } else {
- continue;
- }
-
- $tags[] = array(
- 'name' => $name,
- 'value' => $value,
- );
- } else {
- break;
- }
- }
-
- return $tags;
- }
-
- public static function getTitleTag($html)
- {
- if (preg_match('#(?[^<]+) #i', $html, $matches)) {
- return self::entityDecode($matches['title']);
- }
-
- return '';
- }
-
- public static function entityDecode($html)
- {
- $decoded = $html;
-
- // required to deal with " etc.
- $decoded = html_entity_decode($decoded, ENT_COMPAT, 'UTF-8');
-
- // required to deal with Ӓ etc.
- $convmap = array(0x0, 0x2FFFF, 0, 0xFFFF);
- $decoded = mb_decode_numericentity($decoded, $convmap, 'UTF-8');
-
- return $decoded;
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/Http.php b/library/DevHelper/Helper/ShippableHelper/Http.php
deleted file mode 100644
index 30f10a8..0000000
--- a/library/DevHelper/Helper/ShippableHelper/Http.php
+++ /dev/null
@@ -1,110 +0,0 @@
- 3600,
- ), $options);
-
- $cache = null;
- $cacheKey = '';
- if ($options['cacheTtl'] > 0) {
- $cache = XenForo_Application::getCache();
- }
- if ($cache) {
- $cacheKey = self::_resolveRedirect_getCacheKey($url);
- $resolved = $cache->load($cacheKey, false, true);
- if (!empty($resolved)) {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__METHOD__, sprintf('Resolved via cache. ($url=%s, $limit=%d) -> %s',
- $url, $limit, $resolved));
- }
-
- return unserialize($resolved);
- }
- }
-
- $resolved = self::_resolveRedirect_curl($url, $limit);
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__METHOD__, sprintf('Resolved via curl. ($url=%s, $limit=%d) -> %s',
- $url, $limit, serialize($resolved)));
- }
-
- if (!empty($cache)
- && !empty($cacheKey)
- ) {
- $cache->save(serialize($resolved), $cacheKey, array(), $options['cacheTtl']);
- }
-
- return $resolved;
- }
-
- protected static function _resolveRedirect_curl($url, $limit, array $options = array())
- {
- if (!isset($options['originalUrl'])) {
- $options['originalUrl'] = $url;
- }
- if (!isset($options['originalLimit'])) {
- $options['originalLimit'] = $limit;
- }
-
- if (!parse_url($url, PHP_URL_HOST)) {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__METHOD__, sprintf('Cannot resolve malformed url. ($url=%s, $limit=%d) -> %s',
- $options['originalUrl'], $options['originalLimit'], $url));
- }
-
- return $url;
- }
-
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_NOBODY, true);
- curl_setopt($ch, CURLOPT_HEADER, true);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- $headers = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-
- $redirectLocation = '';
- if ($httpCode >= 300
- && $httpCode < 400
- && preg_match('#Location: (?http.+)\s#', $headers, $matches)
- ) {
- $redirectLocation = $matches['location'];
- }
-
- if ($redirectLocation === '') {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__METHOD__, sprintf('Resolved. ($url=%s, $limit=%d) -> %s',
- $options['originalUrl'], $options['originalLimit'], $url));
- }
-
- return $url;
- }
-
- if ($limit === -1) {
- $nextLimit = -1;
- } elseif ($limit > 0) {
- $nextLimit = $limit - 1;
- } else {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__METHOD__, sprintf('Too many redirects! ($url=%s, $limit=%d) -> %s',
- $options['originalUrl'], $options['originalLimit'], $url));
- }
-
- return $url;
- }
-
- return self::_resolveRedirect_curl($redirectLocation, $nextLimit, $options);
- }
-
- protected static function _resolveRedirect_getCacheKey($url)
- {
- return preg_replace('#[^A-Za-z0-9_]#', '', sprintf('%s_%s', $url, md5(sprintf('%s_%s', $url, __CLASS__))));
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/Image.php b/library/DevHelper/Helper/ShippableHelper/Image.php
deleted file mode 100644
index 52eda51..0000000
--- a/library/DevHelper/Helper/ShippableHelper/Image.php
+++ /dev/null
@@ -1,77 +0,0 @@
- 0 && $height > 0) {
- $size = $width;
- $mode = $height;
- } elseif ($width > 0) {
- $size = $width;
- $mode = bdImage_Integration::MODE_STRETCH_HEIGHT;
- } elseif ($height > 0) {
- $size = $height;
- $mode = bdImage_Integration::MODE_STRETCH_WIDTH;
- }
-
- return bdImage_Integration::buildThumbnailLink($imageUrl, $size, $mode);
- }
-
- $thumbnailPath = self::getThumbnailPath($imageUrl, $width, $height, $dir);
- $thumbnailUrl = XenForo_Link::convertUriToAbsoluteUri(XenForo_Application::$externalDataUrl
- . self::_getThumbnailRelativePath($imageUrl, $width, $height, $dir), true);
- if (file_exists($thumbnailPath)) {
- $thumbnailFileSize = filesize($thumbnailPath);
- if ($thumbnailFileSize > 0) {
- return sprintf('%s?fs=%d', $thumbnailUrl, $thumbnailFileSize);
- }
- }
-
- $coreData = DevHelper_Helper_ShippableHelper_ImageCore::open($imageUrl);
- $coreData = DevHelper_Helper_ShippableHelper_ImageCore::thumbnail($coreData, $width, $height);
- DevHelper_Helper_ShippableHelper_ImageCore::save($coreData, $thumbnailPath);
-
- return sprintf('%s?t=%d', $thumbnailUrl, XenForo_Application::$time);
- }
-
- public static function getThumbnailPath($imageUrl, $width, $height = 0, $dir = null)
- {
- $thumbnailPath = XenForo_Helper_File::getExternalDataPath()
- . self::_getThumbnailRelativePath($imageUrl, $width, $height, $dir);
-
- return $thumbnailPath;
- }
-
- protected static function _getThumbnailRelativePath($imageUrl, $width, $height, $dir)
- {
- $path = parse_url($imageUrl, PHP_URL_PATH);
- $basename = basename($path);
- if (empty($basename)) {
- $basename = md5($imageUrl);
- }
- $fileName = preg_replace('#[^a-zA-Z0-9]#', '', $basename) . md5(serialize(array(
- 'fullPath' => $imageUrl,
- 'width' => $width,
- 'height' => $height,
- )));
- $ext = XenForo_Helper_File::getFileExtension($basename);
- if (!in_array($ext, array('gif', 'jpeg', 'jpg', 'png'), true)) {
- $ext = 'jpg';
- }
- $divider = substr(md5($fileName), 0, 2);
-
- if (empty($dir)) {
- $dir = trim(str_replace('_', '/', substr(__CLASS__, 0, strpos(__CLASS__, '_ShippableHelper_Image'))), '/');
- }
-
- return sprintf('/%s/%sx%s/%s/%s.%s', $dir, $width, $height, $divider, $fileName, $ext);
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/ImageCore.php b/library/DevHelper/Helper/ShippableHelper/ImageCore.php
deleted file mode 100644
index 2372a54..0000000
--- a/library/DevHelper/Helper/ShippableHelper/ImageCore.php
+++ /dev/null
@@ -1,330 +0,0 @@
-get('maxImageResizePixelCount')
- ) {
- $image = XenForo_Image_Abstract::createFromFile($accessiblePath, $imageInfo['typeInt']);
- }
-
- return array(
- 'image' => $image,
- 'imageInfo' => $imageInfo,
- );
- }
-
- /**
- * @param array $data
- * @param string $path
- * @return bool
- */
- public static function save(array $data, $path)
- {
- XenForo_Helper_File::createDirectory(dirname($path), true);
- $outputPath = tempnam(XenForo_Helper_File::getTempDir(), __CLASS__);
-
- /** @noinspection PhpUndefinedMethodInspection */
- $data['image']->output($data['imageInfo']['typeInt'], $outputPath);
- return XenForo_Helper_File::safeRename($outputPath, $path);
- }
-
- /**
- * @param array $data
- * @param int $width
- * @param int $height
- * @return array()
- */
- public static function thumbnail(array $data, $width, $height = 0)
- {
- $image = $data['image'];
- $imageInfo = $data['imageInfo'];
-
- if (empty($image)) {
- // no image input, create a new one
- $size = min(100, max($width, $height));
- $image = XenForo_Image_Abstract::createImage($size, $size);
- $imageInfo['typeInt'] = IMAGETYPE_PNG;
- }
-
- $isGd = $image instanceof XenForo_Image_Gd;
- $isImageMagick = $image instanceof XenForo_Image_ImageMagick_Pecl;
- if ($imageInfo['typeInt'] === IMAGETYPE_GIF && $isImageMagick) {
- /** @noinspection PhpParamsInspection */
- _DevHelper_Helper_ShippableHelper_ImageCore_ImageMagick::dropFrames($image);
- }
-
- if (in_array($width, array('', 0), true) && $height > 0) {
- self::_resizeStretchWidth($image, $height);
- } elseif (in_array($height, array('', 0), true) && $width > 0) {
- self::_resizeStretchHeight($image, $width);
- } elseif ($width > 0 && $height > 0) {
- self::_cropExact($image, $width, $height);
- } elseif ($width > 0) {
- self::_cropSquare($image, $width);
- }
-
- if ($imageInfo['typeInt'] === IMAGETYPE_JPEG) {
- if ($isGd) {
- /** @noinspection PhpParamsInspection */
- _DevHelper_Helper_ShippableHelper_ImageCore_Gd::progressiveJpeg($image);
- } elseif ($isImageMagick) {
- /** @noinspection PhpParamsInspection */
- _DevHelper_Helper_ShippableHelper_ImageCore_ImageMagick::progressiveJpeg($image);
- }
- }
-
- return array(
- 'image' => $image,
- 'imageInfo' => $imageInfo,
- );
- }
-
- /**
- * @param XenForo_Image_Abstract $imageObj
- * @param int $targetHeight
- */
- protected static function _resizeStretchWidth(XenForo_Image_Abstract $imageObj, $targetHeight)
- {
- $targetWidth = $targetHeight / $imageObj->getHeight() * $imageObj->getWidth();
- $imageObj->thumbnail($targetWidth, $targetHeight);
- }
-
- /**
- * @param XenForo_Image_Abstract $imageObj
- * @param int $targetWidth
- */
- protected static function _resizeStretchHeight(XenForo_Image_Abstract $imageObj, $targetWidth)
- {
- $targetHeight = $targetWidth / $imageObj->getWidth() * $imageObj->getHeight();
- $imageObj->thumbnail($targetWidth, $targetHeight);
- }
-
- /**
- * @param XenForo_Image_Abstract $imageObj
- * @param int $targetWidth
- * @param int $targetHeight
- */
- protected static function _cropExact(XenForo_Image_Abstract $imageObj, $targetWidth, $targetHeight)
- {
- $origRatio = $imageObj->getWidth() / $imageObj->getHeight();
- $cropRatio = $targetWidth / $targetHeight;
- if ($origRatio > $cropRatio) {
- $thumbnailHeight = $targetHeight;
- $thumbnailWidth = $thumbnailHeight * $origRatio;
- } else {
- $thumbnailWidth = $targetWidth;
- $thumbnailHeight = $thumbnailWidth / $origRatio;
- }
-
- if ($thumbnailWidth <= $imageObj->getWidth()
- && $thumbnailHeight <= $imageObj->getHeight()
- ) {
- $imageObj->thumbnail($thumbnailWidth, $thumbnailHeight);
- self::_cropCenter($imageObj, $targetWidth, $targetHeight);
- } else {
- if ($origRatio > $cropRatio) {
- self::_cropCenter($imageObj, $imageObj->getHeight() * $cropRatio, $imageObj->getHeight());
- } else {
- self::_cropCenter($imageObj, $imageObj->getWidth(), $imageObj->getWidth() / $cropRatio);
- }
- }
- }
-
- /**
- * @param XenForo_Image_Abstract $imageObj
- * @param int $target
- */
- protected static function _cropSquare(XenForo_Image_Abstract $imageObj, $target)
- {
- $imageObj->thumbnailFixedShorterSide($target);
- self::_cropCenter($imageObj, $target, $target);
- }
-
- /**
- * @param XenForo_Image_Abstract $imageObj
- * @param int $cropWidth
- * @param int $cropHeight
- */
- protected static function _cropCenter(XenForo_Image_Abstract $imageObj, $cropWidth, $cropHeight)
- {
- if (self::$cropTopLeft) {
- // revert to top left cropping (old version behavior)
- /** @noinspection PhpParamsInspection */
- $imageObj->crop(0, 0, $cropWidth, $cropHeight);
- return;
- }
-
- $width = $imageObj->getWidth();
- $height = $imageObj->getHeight();
- $x = floor(($width - $cropWidth) / 2);
- $y = floor(($height - $cropHeight) / 2);
-
- /** @noinspection PhpParamsInspection */
- $imageObj->crop($x, $y, $cropWidth, $cropHeight);
- }
-
- protected static function _getAccessiblePath($path)
- {
- if (!parse_url($path, PHP_URL_HOST)) {
- return $path;
- }
-
- $boardUrl = XenForo_Application::getOptions()->get('boardUrl');
- if (strpos($path, '..') === false
- && strpos($path, $boardUrl) === 0
- ) {
- $localFilePath = self::_getLocalFilePath(substr($path, strlen($boardUrl)));
- if (self::_imageExists($localFilePath)) {
- return $localFilePath;
- }
- }
-
- if (preg_match('#attachments/(.+\.)*(?\d+)/#', $path, $matches)) {
- $fullIndex = XenForo_Link::buildPublicLink('full:index');
- $canonicalIndex = XenForo_Link::buildPublicLink('canonical:index');
- if (strpos($path, $fullIndex) === 0 || strpos($path, $canonicalIndex) === 0) {
- $attachmentDataFilePath = self::_getAttachmentDataFilePath($matches['id']);
- if (self::_imageExists($attachmentDataFilePath)) {
- return $attachmentDataFilePath;
- }
- }
- }
-
- return DevHelper_Helper_ShippableHelper_TempFile::download($path);
- }
-
- protected static function _getLocalFilePath($path)
- {
- // remove query parameters
- $path = preg_replace('#(\?|\#).*$#', '', $path);
- if (strlen($path) === 0) {
- return $path;
- }
-
- $extension = XenForo_Helper_File::getFileExtension($path);
- if (!in_array($extension, array('gif', 'jpeg', 'jpg', 'png'), true)) {
- return '';
- }
-
- /** @var XenForo_Application $app */
- $app = XenForo_Application::getInstance();
- $path = sprintf('%s/%s', rtrim($app->getRootDir(), '/'), ltrim($path, '/'));
-
- return $path;
- }
-
- protected static function _getAttachmentDataFilePath($attachmentId)
- {
- /** @var XenForo_Model_Attachment $attachmentModel */
- static $attachmentModel = null;
- static $attachments = array();
-
- if ($attachmentModel === null) {
- $attachmentModel = XenForo_Model::create('XenForo_Model_Attachment');
- }
-
- if (!isset($attachments[$attachmentId])) {
- $attachments[$attachmentId] = $attachmentModel->getAttachmentById($attachmentId);
- }
-
- if (empty($attachments[$attachmentId])) {
- return '';
- }
-
- return $attachmentModel->getAttachmentDataFilePath($attachments[$attachmentId]);
- }
-
- protected static function _imageExists($path)
- {
- if (!is_string($path) || strlen($path) === 0) {
- // invalid path
- return false;
- }
-
- $pathSize = @filesize($path);
- if (!is_int($pathSize)) {
- // file not exists
- return false;
- }
-
- // according to many sources, no valid image can be smaller than 14 bytes
- // http://garethrees.org/2007/11/14/pngcrush/
- // https://github.com/mathiasbynens/small/blob/master/gif.gif
- return $pathSize >= 14;
- }
-
- protected static function _getImageInfo($path)
- {
- $imageInfo = array();
-
- $fileSize = @filesize($path);
- if (!empty($fileSize)) {
- $imageInfo['fileSize'] = $fileSize;
- }
-
- if (!empty($imageInfo['fileSize'])
- && $info = @getimagesize($path)
- ) {
- $imageInfo['width'] = $info[0];
- $imageInfo['height'] = $info[1];
- $imageInfo['typeInt'] = $info[2];
- }
-
- return $imageInfo;
- }
-}
-
-class _DevHelper_Helper_ShippableHelper_ImageCore_Gd extends XenForo_Image_Gd
-{
- public static function progressiveJpeg(XenForo_Image_Gd $obj)
- {
- imageinterlace($obj->_image, 1);
- }
-}
-
-class _DevHelper_Helper_ShippableHelper_ImageCore_ImageMagick extends XenForo_Image_ImageMagick_Pecl
-{
- public static function dropFrames(XenForo_Image_ImageMagick_Pecl $obj)
- {
- $image = $obj->_image;
-
- if ($image->count() > 1) {
- $newImage = null;
-
- foreach ($image as $frame) {
- $newImage = new Imagick();
- $newImage->addImage($frame->getImage());
- break;
- }
-
- if ($newImage !== null) {
- $obj->_image = $newImage;
- $image->destroy();
- }
- }
- }
-
- public static function progressiveJpeg(XenForo_Image_ImageMagick_Pecl $obj)
- {
- $obj->_image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/ImageSize.php b/library/DevHelper/Helper/ShippableHelper/ImageSize.php
deleted file mode 100644
index 2067660..0000000
--- a/library/DevHelper/Helper/ShippableHelper/ImageSize.php
+++ /dev/null
@@ -1,542 +0,0 @@
- 2000
- || strpos($uri, ' ') !== false
- || strpos($uri, 'data:') === 0
- ) {
- return array(
- 'uri' => $uri,
- 'width' => 0,
- 'height' => 0,
- 'timestamp' => time(),
- );
- }
-
- $cacheId = __CLASS__ . md5($uri);
- $cache = ($ttl > 0 ? XenForo_Application::getCache() : null);
-
- if ($cache) {
- $cachedData = $cache->load($cacheId);
- if (is_string($cachedData)) {
- $cachedData = unserialize($cachedData);
- }
- if (!is_array($cachedData)) {
- $cachedData = array();
- }
- if (!empty($cachedData['uri'])
- && $cachedData['uri'] === $uri
- && XenForo_Application::$time - $cachedData['timestamp'] < $ttl
- ) {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('$uri=%s; CACHE HIT', $uri));
- }
- return $cachedData;
- }
- }
-
- $originalUri = $uri;
- $requestPaths = XenForo_Application::get('requestPaths');
- if (strpos($uri, $requestPaths['fullBasePath']) === 0) {
- $uri = substr($uri, strlen($requestPaths['fullBasePath']));
- }
-
- $data = self::_calculate($uri);
- if (empty($data['width'])
- || empty($data['height'])
- ) {
- $absoluteUri = XenForo_Link::convertUriToAbsoluteUri($uri, true);
- if ($absoluteUri != $uri) {
- $data = self::_calculate($absoluteUri);
- }
- }
-
- if (!empty($data['uri'])
- && $data['uri'] != $originalUri
- ) {
- $data['_calculateUri'] = $data['uri'];
- }
- $data['uri'] = $originalUri;
-
- if ($cache) {
- $cache->save(serialize($data), $cacheId, array(), $ttl);
- }
-
- return $data;
- }
-
- public static function getDataUriMatchesRatio($uri, $rgba = null)
- {
- if (!function_exists('imagecreatetruecolor')) {
- return '';
- }
-
- $calculated = self::calculate($uri);
- if ($calculated['width'] < 1 || $calculated['height'] < 1) {
- return '';
- }
-
- return self::getDataUriAtSize($calculated['width'], $calculated['height'], $rgba);
- }
-
- public static function getDataUriAtSize($width, $height, $rgba = null)
- {
- if (!function_exists('imagecreatetruecolor') || $width < 1 || $height < 1) {
- return '';
- }
-
- $gcd = self::_findGreatestCommonDivisor($width, $height);
- $width /= $gcd;
- $height /= $gcd;
-
- $image = imagecreate($width, $height);
- imagealphablending($image, false);
- imagesavealpha($image, true);
- if (!is_array($rgba) || count($rgba) < 3) {
- $color = imagecolorallocatealpha($image, 255, 255, 255, 127);
- } else {
- $rgba = array_values($rgba);
- $r = max(0, min(255, $rgba[0] < 1 ? floor(255 * $rgba[0]) : $rgba[0]));
- $g = max(0, min(255, $rgba[1] < 1 ? floor(255 * $rgba[1]) : $rgba[0]));
- $b = max(0, min(255, $rgba[2] < 1 ? floor(255 * $rgba[2]) : $rgba[0]));
- $a = isset($rgba[3]) ? max(0, min(127, $rgba[3] < 1 ? floor(127 * $rgba[3]) : $rgba[0])) : 0;
- $color = imagecolorallocatealpha($image, $r, $g, $b, $a);
- }
- imagefilledrectangle($image, 0, 0, $width, $height, $color);
-
- ob_start();
- imagepng($image);
- $imageBytes = ob_get_contents();
- ob_end_clean();
-
- return 'data:image/png;base64,' . base64_encode($imageBytes);
- }
-
- protected static function _calculate($uri)
- {
- /** @var self $instance */
- static $instance = null;
-
- $startTime = microtime(true);
-
- if (preg_match('#^(?.+)?attachments/(.+\.)*(?\d+)/#', $uri, $matches)
- && ($matches['prefix'] === ''
- || strpos($matches['prefix'], XenForo_Link::buildPublicLink('full:index')) === 0
- || strpos($matches['prefix'], XenForo_Link::buildPublicLink('canonical:index')) === 0
- )
- ) {
- $attachmentResult = self::_calculateForAttachment($uri, $matches['id']);
- if (!empty($attachmentResult['width'])
- && !empty($attachmentResult['height'])
- ) {
- return $attachmentResult;
- }
- }
-
- if ($instance === null) {
- $instance = new self($uri);
- } else {
- $instance->close();
- $instance->load($uri);
- }
-
- list($width, $height) = $instance->getSize();
-
- if (XenForo_Application::debugMode()) {
- $elapsedTime = microtime(true) - $startTime;
- XenForo_Helper_File::log(__CLASS__, sprintf('$uri=%s; $width=%d, $height=%d; $elapsedTime=%.6f',
- $uri, $width, $height, $elapsedTime));
- }
-
- return array(
- 'uri' => $uri,
- 'width' => $width,
- 'height' => $height,
- 'timestamp' => time(),
- );
- }
-
- protected static function _calculateForAttachment($uri, $attachmentId)
- {
- $startTime = microtime(true);
-
- /** @var XenForo_Model_Attachment $attachmentModel */
- static $attachmentModel = null;
- static $attachments = array();
-
- if ($attachmentModel === null) {
- $attachmentModel = XenForo_Model::create('XenForo_Model_Attachment');
- }
-
- if (!isset($attachments[$attachmentId])) {
- $attachments[$attachmentId] = $attachmentModel->getAttachmentById($attachmentId);
- }
-
- $width = 0;
- if (!empty($attachments[$attachmentId]['width'])) {
- $width = intval($attachments[$attachmentId]['width']);
- }
-
- $height = 0;
- if (!empty($attachments[$attachmentId]['height'])) {
- $height = intval($attachments[$attachmentId]['height']);
- }
-
- if (XenForo_Application::debugMode()) {
- $elapsedTime = microtime(true) - $startTime;
- XenForo_Helper_File::log(__CLASS__, sprintf('$uri=%s; $attachmentId=%d, $width=%d, $height=%d;'
- . ' $elapsedTime=%.6f', $uri, $attachmentId, $width, $height, $elapsedTime));
- }
-
- return array(
- 'uri' => $uri,
- 'attachmentId' => $attachmentId,
- 'width' => $width,
- 'height' => $height,
- 'timestamp' => time(),
- );
- }
-
- protected static function _findGreatestCommonDivisor($a, $b)
- {
- $mod = $a % $b;
- if ($mod === 0) {
- return $b;
- } else {
- return self::_findGreatestCommonDivisor($b, $mod);
- }
- }
-
- // https://github.com/tommoor/fastimage/commit/7bf53fcfebb5bc04b78a8cf23862778256de2241
- private $pos = 0;
- private $str;
- private $type;
- private $handle;
-
- private function __construct($uri = null)
- {
- if ($uri) {
- $this->load($uri);
- }
- }
-
- private function load($uri)
- {
- if ($this->handle) {
- $this->close();
- }
-
- $context = null;
- $host = parse_url($uri, PHP_URL_HOST);
- if ($host) {
- $httpContext = array('timeout' => 2.0);
- $httpContext['header'] = "Referer: http://$host\r\n";
-
- if (!empty($_SERVER['HTTP_USER_AGENT'])) {
- $httpContext['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
- } else {
- $httpContext['user_agent'] = 'Mozilla/4.0 (MSIE 6.0; Windows NT 5.0)';
- }
-
- $context = stream_context_create(array(
- 'http' => $httpContext,
- 'ssl' => array(
- 'verify_peer' => false,
- 'verify_peer_name' => false,
- 'allow_self_signed' => true,
- )
- ));
- }
-
- if ($context != null) {
- $this->handle = @fopen($uri, 'rb', null, $context);
- } else {
- $this->handle = @fopen($uri, 'rb');
- }
- }
-
- private function close()
- {
- if ($this->handle) {
- if (is_resource($this->handle)) {
- fclose($this->handle);
- }
-
- $this->handle = null;
- $this->type = null;
- $this->str = null;
- }
- }
-
- private function getSize()
- {
- $this->pos = 0;
- if (!$this->getType()) {
- return array(0, 0);
- }
-
- $size = $this->parseSize();
- if (!is_array($size) || count($size) < 2) {
- return array(0, 0);
- }
-
- return array_map('intval', array_values($size));
- }
-
- private function getType()
- {
- $this->pos = 0;
-
- if (!$this->type) {
- switch ($this->getChars(2)) {
- case 'BM':
- return $this->type = 'bmp';
- case 'GI':
- return $this->type = 'gif';
- case chr(0xFF) . chr(0xd8):
- return $this->type = 'jpeg';
- case chr(0x89) . 'P':
- return $this->type = 'png';
- case 'II':
- case 'MM':
- return $this->type = 'tiff';
- case 'RI':
- return $this->type = 'webp';
- default:
- return false;
- }
- }
-
- return $this->type;
- }
-
- private function parseSize()
- {
- $this->pos = 0;
-
- $method = 'parseSizeFor' . strtoupper($this->type);
- if (is_callable(array($this, $method))) {
- return call_user_func(array($this, $method));
- }
-
- return null;
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForPNG()
- {
- $chars = $this->getChars(25);
-
- return unpack('N*', substr($chars, 16, 8));
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForGIF()
- {
- $chars = $this->getChars(11);
-
- return unpack('S*', substr($chars, 6, 4));
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForBMP()
- {
- $chars = $this->getChars(29);
- $chars = substr($chars, 14, 14);
- $type = unpack('C', $chars);
-
- return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8));
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForJPEG()
- {
- $state = null;
- $skip = 0;
-
- while (true) {
- switch ($state) {
- default:
- $this->getChars(2);
- $state = 'started';
- break;
-
- case 'started':
- $b = $this->getByte();
- if ($b === false) {
- return false;
- }
-
- $state = $b == 0xFF ? 'sof' : 'started';
- break;
-
- case 'sof':
- $b = $this->getByte();
- if (in_array($b, range(0xe0, 0xef), true)) {
- $state = 'skipframe';
- } elseif (in_array($b, array_merge(range(0xC0, 0xC3), range(0xC5, 0xC7),
- range(0xC9, 0xCB), range(0xCD, 0xCF)), true)) {
- $state = 'readsize';
- } elseif ($b === 0xFF) {
- $state = 'sof';
- } else {
- $state = 'skipframe';
- }
- break;
-
- case 'skipframe':
- $skip = $this->readInt($this->getChars(2)) - 2;
- $state = 'doskip';
- break;
-
- case 'doskip':
- $this->getChars($skip);
- $state = 'started';
- break;
-
- case 'readsize':
- $c = $this->getChars(7);
-
- return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
- }
- }
-
- return array(0, 0);
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForWEBP()
- {
- $chars = $this->getChars(30);
- $result = unpack('C12/S9', $chars);
-
- return array($result['8'], $result['9']);
- }
-
- /** @noinspection PhpUnusedPrivateMethodInspection */
- private function parseSizeForTIFF()
- {
- $byteOrder = $this->getChars(2);
- switch ($byteOrder) {
- case 'II':
- $short = 'v';
- $long = 'V';
- break;
- case 'MM':
- $short = 'n';
- $long = 'N';
- break;
- default:
- return false;
- break;
- }
-
- $this->getChars(2);
- $offset = current(unpack($long, $this->getChars(4)));
-
- $this->getChars($offset - 8);
- $tagCount = current(unpack($short, $this->getChars(2)));
-
- for ($i = $tagCount; $i > 0; $i--) {
- $type = current(unpack($short, $this->getChars(2)));
- $this->getChars(6);
- $data = current(unpack($short, $this->getChars(2)));
- switch ($type) {
- case 0x0100:
- $width = $data;
- break;
- case 0x0101:
- $height = $data;
- break;
- case 0x0112:
- $orientation = $data;
- break;
- }
- if (isset($width) && isset($height) && isset($orientation)) {
- if ($orientation >= 5) {
- return array($height, $width);
- }
- return array($width, $height);
- }
- $this->getChars(2);
- }
-
- return array(0, 0);
- }
-
- private function getChars($n)
- {
- if (!is_resource($this->handle)) {
- return false;
- }
-
- $response = null;
-
- // do we need more data?
- if ($this->pos + $n - 1 >= strlen($this->str)) {
- $end = ($this->pos + $n);
-
- while (strlen($this->str) < $end && $response !== false) {
- // read more from the file handle
- $need = $end - ftell($this->handle);
-
- if ($response = fread($this->handle, $need)) {
- $this->str .= $response;
- } else {
- return false;
- }
- }
- }
-
- $result = substr($this->str, $this->pos, $n);
- $this->pos += $n;
-
- if (function_exists('mb_convert_encoding')) {
- $result = mb_convert_encoding($result, '8BIT', '7BIT');
- }
-
- return $result;
- }
-
- private function getByte()
- {
- $c = $this->getChars(1);
- if (!is_string($c) || strlen($c) === 0) {
- return false;
- }
-
- $b = unpack('C', $c);
-
- return reset($b);
- }
-
- private function readInt($str)
- {
- $size = unpack('C*', $str);
- if (!isset($size[1]) || !isset($size[2])) {
- return 0;
- }
-
- return ($size[1] << 8) + $size[2];
- }
-
- public function __destruct()
- {
- $this->close();
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/S3.php b/library/DevHelper/Helper/ShippableHelper/S3.php
deleted file mode 100644
index 858f077..0000000
--- a/library/DevHelper/Helper/ShippableHelper/S3.php
+++ /dev/null
@@ -1,227 +0,0 @@
-_region = $region;
-
- switch ($region) {
- case 'us-east-1':
- // default endpoint is correct for this region
- break;
- default:
- $this->setEndpoint(sprintf('http://s3-%s.amazonaws.com', $region));
- }
- }
-
- public function setSignatureAws4($enabled)
- {
- $this->_signatureAws4 = $enabled;
- }
-
- public function _makeRequest($method, $path = '', $params = null, $headers = array(), $data = null)
- {
- if (empty($params)) {
- $params = array();
- }
-
- if (!is_array($headers)) {
- $headers = array($headers);
- }
-
- if (is_resource($data)) {
- throw new Zend_Service_Amazon_S3_Exception("No support for stream data");
- }
- $data = strval($data);
-
- // build the end point (with path)
- $endpoint = clone($this->_endpoint);
- $endpoint->setPath('/' . $path);
-
- $retryCount = 0;
-
- if ($this->_signatureAws4) {
- if (isset($headers['Content-MD5'])) {
- unset($headers['Content-MD5']);
- }
-
- $headers['x-amz-content-sha256'] = Zend_Crypt::hash('sha256', $data);
- $headers['x-amz-date'] = sprintf(
- '%sT%sZ',
- gmdate('Ymd', XenForo_Application::$time),
- gmdate('His', XenForo_Application::$time)
- );
- $headers['Host'] = parse_url($endpoint, PHP_URL_HOST);
-
- $this->addSignatureAws4($method, $path, $params, $headers);
- } else {
- $headers['Date'] = gmdate(DATE_RFC1123, time());
- self::addSignature($method, $path, $headers);
- }
-
- $client = self::getHttpClient();
-
- $client->resetParameters();
- $client->setUri($endpoint);
- $client->setAuth(false);
- $client->setHeaders($headers);
-
- if (is_array($params)) {
- foreach ($params as $name => $value) {
- $client->setParameterGet($name, $value);
- }
- }
-
- if (($method == 'PUT') && ($data !== null)) {
- if (!isset($headers['Content-type'])) {
- $headers['Content-type'] = self::getMimeType($path);
- }
- $client->setRawData($data, $headers['Content-type']);
- }
-
- do {
- $retry = false;
-
- $response = $client->request($method);
- $responseCode = $response->getStatus();
-
- if (XenForo_Application::debugMode() || $responseCode != 200) {
- XenForo_Helper_File::log(__METHOD__, sprintf(
- "%s %s %s -> %d %s\n\n",
- $method,
- $endpoint->getUri(),
- var_export($headers, true),
- $responseCode,
- $responseCode != 200
- ? $response->getBody()
- : sprintf('Body(length=%d)', strlen($response->getBody()))
- ));
- }
-
- // some 5xx errors are expected, so retry automatically
- if ($responseCode >= 500 && $responseCode < 600 && $retryCount <= 5) {
- $retry = true;
- $retryCount++;
- sleep($retryCount / 4 * $retryCount);
- } elseif ($responseCode == 307) {
- // need to redirect, new S3 endpoint given
- // this should never happen as Zend_Http_Client will redirect automatically
- } elseif ($responseCode == 100) {
- // 'OK to Continue';
- }
- } while ($retry);
-
- return $response;
- }
-
- protected function addSignatureAws4($method, $path, array $params, array &$headers)
- {
- // http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
- // task 1: create a canonical request
- $canonicalQueryArray = array();
- if (!empty($params)) {
- ksort($params);
- foreach ($params as $paramKey => $paramValue) {
- $canonicalQueryArray[] = sprintf('%s=%s', urlencode($paramKey), urlencode($paramValue));
- }
- }
- $canonicalQueryString = implode('&', $canonicalQueryArray);
-
- $canonicalHeaders = '';
- $signedHeadersArray = array();
- $hashedPayload = '';
- $timestamp = '';
- $headerKeys = array_combine(array_map('strtolower', array_keys($headers)), array_keys($headers));
- ksort($headerKeys);
- foreach ($headerKeys as $headerKeyLower => $headerKey) {
- $canonicalHeaders .= sprintf("%s:%s\n", $headerKeyLower, $headers[$headerKey]);
- $signedHeadersArray[] = $headerKeyLower;
-
- switch ($headerKey) {
- case 'x-amz-content-sha256':
- $hashedPayload = $headers[$headerKey];
- break;
- case 'x-amz-date':
- $timestamp = $headers[$headerKey];
- break;
- }
- }
- $signedHeadersString = implode(';', $signedHeadersArray);
-
- $canonicalRequest = sprintf(
- "%s\n/%s\n%s\n%s\n%s\n%s",
- $method,
- $path,
- $canonicalQueryString,
- $canonicalHeaders,
- $signedHeadersString,
- $hashedPayload
- );
-
- // task 2: create a string to sign
- $date = substr($timestamp, 0, strpos($timestamp, 'T'));
- $scope = sprintf(
- '%s/%s/s3/aws4_request',
- $date,
- $this->_region
- );
- $stringToSign = sprintf(
- "AWS4-HMAC-SHA256\n%s\n%s\n%s",
- $timestamp,
- $scope,
- Zend_Crypt::hash('sha256', $canonicalRequest)
- );
-
- // task 3: calculate signature
- $dateKey = Zend_Crypt_Hmac::compute('AWS4' . $this->_getSecretKey(), 'sha256',
- $date, Zend_Crypt_Hmac::BINARY);
- $dateRegionKey = Zend_Crypt_Hmac::compute($dateKey,
- 'sha256', $this->_region, Zend_Crypt_Hmac::BINARY);
- $dateRegionServiceKey = Zend_Crypt_Hmac::compute($dateRegionKey,
- 'sha256', 's3', Zend_Crypt_Hmac::BINARY);
- $signingKey = Zend_Crypt_Hmac::compute($dateRegionServiceKey,
- 'sha256', 'aws4_request', Zend_Crypt_Hmac::BINARY);
-
- $signature = Zend_Crypt_Hmac::compute($signingKey, 'sha256', $stringToSign);
- $headers['Authorization'] = sprintf(
- 'AWS4-HMAC-SHA256 Credential=%s/%s,SignedHeaders=%s,Signature=%s',
- $this->_getAccessKey(),
- $scope,
- $signedHeadersString,
- $signature
- );
-
- return $signature;
- }
-
- public static function getRegions()
- {
- // http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
- return array(
- 'us-east-1' => 'US East (N. Virginia)',
- 'us-east-2' => 'US East (Ohio)',
- 'us-west-1' => 'US West (N. California)',
- 'us-west-2' => 'US West (Oregon)',
- 'ca-central-1' => 'Canada (Central)',
- 'ap-south-1' => 'Asia Pacific (Mumbai)',
- 'ap-northeast-2' => 'Asia Pacific (Seoul)',
- 'ap-southeast-1' => 'Asia Pacific (Singapore)',
- 'ap-southeast-2' => 'Asia Pacific (Sydney)',
- 'ap-northeast-1' => 'Asia Pacific (Tokyo)',
- 'eu-central-1' => 'EU (Frankfurt)',
- 'eu-west-1' => 'EU (Ireland)',
- 'eu-west-2' => 'EU (London)',
- 'sa-east-1' => 'South America (São Paulo)',
- );
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/String.php b/library/DevHelper/Helper/ShippableHelper/String.php
deleted file mode 100644
index 730a7c2..0000000
--- a/library/DevHelper/Helper/ShippableHelper/String.php
+++ /dev/null
@@ -1,26 +0,0 @@
- 1024 && $unitKey < count($units) - 1) {
- $bytes /= 1024;
- $unitKey++;
- }
-
- // get float with one decimal place
- // truncate if it's insignificant though
- $value = sprintf('%.1f', $bytes);
- $value = preg_replace('#\.0$#', '', $value);
-
- return sprintf('%s %s', $value, $units[$unitKey]);
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/TempFile.php b/library/DevHelper/Helper/ShippableHelper/TempFile.php
deleted file mode 100644
index e82e72b..0000000
--- a/library/DevHelper/Helper/ShippableHelper/TempFile.php
+++ /dev/null
@@ -1,213 +0,0 @@
- '',
- 'userAgent' => '',
- 'timeOutInSeconds' => 0,
- 'maxRedirect' => 3,
- 'maxDownloadSize' => XenForo_Application::getOptions()->get('attachmentMaxFileSize') * 1024,
- 'secured' => 0,
- );
-
- $tempFile = trim(strval($options['tempFile']));
- $managedTempFile = false;
- if (strlen($tempFile) === 0) {
- $tempFile = tempnam(XenForo_Helper_File::getTempDir(), self::_getPrefix());
- $managedTempFile = true;
- }
- if (strlen($tempFile) === 0) {
- return false;
- }
-
- if (isset(self::$_cached[$url])
- && filesize(self::$_cached[$url]) > 0
- ) {
- if ($managedTempFile) {
- unlink($tempFile);
- return self::$_cached[$url];
- } else {
- copy(self::$_cached[$url], $tempFile);
- return $tempFile;
- }
- }
-
- if ($managedTempFile) {
- self::cache($url, $tempFile);
- }
-
- self::$_maxDownloadSize = $options['maxDownloadSize'];
-
- $fh = fopen($tempFile, 'wb');
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_FILE, $fh);
- curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_NOPROGRESS, 0);
- curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, array(__CLASS__, 'download_curlProgressFunction'));
-
- if (!empty($options['userAgent'])) {
- curl_setopt($ch, CURLOPT_USERAGENT, $options['userAgent']);
- }
- if ($options['timeOutInSeconds'] > 0) {
- curl_setopt($ch, CURLOPT_TIMEOUT, $options['timeOutInSeconds']);
- }
- if ($options['maxRedirect'] > 0) {
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_MAXREDIRS, $options['maxRedirect']);
- } else {
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
- }
- if ($options['secured'] === 0) {
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
- }
-
- curl_exec($ch);
- $curlInfo = curl_getinfo($ch);
- curl_close($ch);
- fclose($fh);
-
- $downloaded = true;
- if (!isset($curlInfo['http_code'])
- || $curlInfo['http_code'] < 200
- || $curlInfo['http_code'] >= 300
- ) {
- // no http response status / non success status, must be an error
- $downloaded = false;
- }
-
- $fileSize = filesize($tempFile);
- if ($downloaded && $fileSize === 0) {
- clearstatcache();
- $fileSize = filesize($tempFile);
- }
-
- if ($downloaded
- && isset($curlInfo['size_download'])
- && $fileSize !== intval($curlInfo['size_download'])
- ) {
- // file size reported by our system seems to be off, probably a write error
- $downloaded = false;
- }
-
- if ($downloaded
- && isset($curlInfo['download_content_length'])
- && $curlInfo['download_content_length'] > 0
- && $fileSize !== intval($curlInfo['download_content_length'])
- ) {
- // file size is different from Content-Length header, probably a cancelled download (or corrupted)
- $downloaded = false;
- }
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, call_user_func_array('sprintf', array(
- 'download %s -> %s, %s, %s',
- $url,
- $tempFile,
- ($downloaded ? 'succeeded' : 'failed'),
- json_encode($curlInfo),
- )));
- }
-
- if ($downloaded) {
- return $tempFile;
- } else {
- file_put_contents($tempFile, '');
- return false;
- }
- }
-
- public static function download_curlProgressFunction($downloadSize, $downloaded)
- {
- return ((self::$_maxDownloadSize > 0
- && ($downloadSize > self::$_maxDownloadSize
- || $downloaded > self::$_maxDownloadSize))
- ? 1 : 0);
- }
-
- public static function deleteAllCached()
- {
- foreach (self::$_cached as $url => $tempFile) {
- if (XenForo_Application::debugMode()) {
- $fileSize = @filesize($tempFile);
- }
-
- $deleted = @unlink($tempFile);
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, call_user_func_array('sprintf', array(
- 'delete %s -> %s, %s, %d bytes',
- $url,
- $tempFile,
- ($deleted ? 'succeeded' : 'failed'),
- (!empty($fileSize) ? $fileSize : 0),
- )));
- }
- }
-
- self::$_cached = array();
- }
-
- protected static function _getPrefix()
- {
- static $prefix = null;
-
- if ($prefix === null) {
- $prefix = strtolower(preg_replace('#[^A-Z]#', '', __CLASS__)) . '_';
- }
-
- return $prefix;
- }
-
-}
diff --git a/library/DevHelper/Helper/ShippableHelper/Updater.php b/library/DevHelper/Helper/ShippableHelper/Updater.php
deleted file mode 100644
index 6a0e0c4..0000000
--- a/library/DevHelper/Helper/ShippableHelper/Updater.php
+++ /dev/null
@@ -1,195 +0,0 @@
- array(),
- 'onPreRoute' => array(),
- 'setupMethod' => array(),
- 'addOns' => array(),
- );
- }
-
- $GLOBALS[self::KEY]['version'][$apiUrl][self::$_version]
- = 'DevHelper_Helper_ShippableHelper_UpdaterCore';
-
- if (!isset($GLOBALS[self::KEY]['onPreRoute'][$apiUrl])) {
- $GLOBALS[self::KEY]['onPreRoute'][$apiUrl] = create_function('$fc',
- __CLASS__ . '::onPreRoute($fc, ' . var_export($config, true) . ');');
- XenForo_CodeEvent::addListener('front_controller_pre_route',
- $GLOBALS[self::KEY]['onPreRoute'][$apiUrl]);
- }
-
- $GLOBALS[self::KEY][$apiUrl]['addOns'][$addOnId] = true;
- }
-
- /**
- * Removes trace of Updater if it is no longer needed.
- *
- * @param string|null $apiUrl
- * @param string|null $addOnId
- * @throws Zend_Exception
- */
- public static function onUninstall($apiUrl = null, $addOnId = null)
- {
- if (XenForo_Application::$versionId < 1020000) {
- // only run if XenForo is at least 1.2.0
- return;
- }
-
- if (!is_string($apiUrl)) {
- // use default api url if nothing provided
- $apiUrl = self::API_URL;
- }
-
- $addOnId = self::_getAddOnId($addOnId);
- $config = self::_readConfig($addOnId, $apiUrl);
- $addOns = XenForo_Application::get('addOns');
-
- $activeAddOnIds = array();
- foreach ($config['addOnIds'] as $configAddOnId) {
- if ($configAddOnId === $addOnId
- || !isset($addOns[$configAddOnId])
- ) {
- continue;
- }
-
- $activeAddOnIds[] = $configAddOnId;
- }
-
- if (empty($activeAddOnIds)) {
- // no active add-ons found, trigger uninstall routine
- DevHelper_Helper_ShippableHelper_UpdaterCore::_uninstallSelf($apiUrl);
- }
- }
-
- /**
- * Finds the latest Updater version and let it setup.
- * THIS METHOD SHOULD NOT BE ALTERED BETWEEN VERSIONS.
- *
- * @param XenForo_FrontController $fc
- * @param array $config
- *
- * @internal
- */
- public static function onPreRoute(
- /** @noinspection PhpUnusedParameterInspection */
- XenForo_FrontController $fc,
- array $config
- ) {
- if (empty($GLOBALS[self::KEY]['version'][$config['apiUrl']])) {
- return;
- }
- $versions = $GLOBALS[self::KEY]['version'][$config['apiUrl']];
-
- ksort($versions);
- $latest = array_pop($versions);
-
- call_user_func(array($latest, 'bootstrap'), $config);
- }
-
- public static function getConfigOptionId($apiUrl)
- {
- $configOptionId = sprintf('updater%s%s', str_replace(' ', '', ucwords(preg_replace('#[^A-Za-z]+#', ' ',
- parse_url($apiUrl, PHP_URL_HOST)))), md5($apiUrl));
- if (strlen($configOptionId) > 50) {
- $configOptionId = 'updater_' . md5($configOptionId);
- }
-
- return $configOptionId;
- }
-
- private static function _getAddOnId($addOnId)
- {
- if ($addOnId !== null) {
- return $addOnId;
- }
-
- $clazz = __CLASS__;
- $strPos = strpos($clazz, '_ShippableHelper_');
- if ($strPos === false) {
- throw new XenForo_Exception('Unable to determine $addOnId');
- }
-
- return substr($clazz, 0, $strPos);
- }
-
- private static function _readConfig($addOnId, $apiUrl)
- {
- $options = XenForo_Application::getOptions();
-
- $configOptionId = self::getConfigOptionId($apiUrl);
- $config = $options->get($configOptionId);
-
- if ($config === null) {
- $config = array(
- 'version' => 0,
- 'apiUrl' => $apiUrl,
- 'addOnIds' => array($addOnId),
-
- 'enabled' => false,
- 'ignored' => array(),
- );
- }
-
- return $config;
- }
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/ShippableHelper/UpdaterCore.php b/library/DevHelper/Helper/ShippableHelper/UpdaterCore.php
deleted file mode 100644
index 586771d..0000000
--- a/library/DevHelper/Helper/ShippableHelper/UpdaterCore.php
+++ /dev/null
@@ -1,794 +0,0 @@
-$config.apiUrlviewName) {
- case 'XenForo_ViewAdmin_AddOn_List':
- self::_onAddOnList($controllerResponse, $containerParams);
- break;
- case 'XenForo_ViewAdmin_AddOn_Upgrade':
- self::_onAddOnUpgrade($controllerResponse);
- break;
- }
- }
-
- private static function _onAddOnList(XenForo_ControllerResponse_View &$controllerResponse, array &$containerParams)
- {
- $message = '';
-
- if (!empty($_GET[self::PARAM_ENABLE])
- && $_GET[self::PARAM_ENABLE] === self::$_config['apiUrl']
- ) {
- self::$_config['configured'] = true;
- self::$_config['enabled'] = true;
- self::_saveConfig(self::$_config);
- $controllerResponse = new XenForo_ControllerResponse_Redirect();
- $controllerResponse->redirectType = XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED;
- $controllerResponse->redirectTarget = XenForo_Link::buildAdminLink('add-ons');
- return;
- }
-
- if (!empty($_GET[self::PARAM_DISABLE])
- && $_GET[self::PARAM_DISABLE] === self::$_config['apiUrl']
- ) {
- self::$_config['configured'] = true;
- self::$_config['enabled'] = false;
- self::_saveConfig(self::$_config);
- $controllerResponse = new XenForo_ControllerResponse_Redirect();
- $controllerResponse->redirectType = XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED;
- $controllerResponse->redirectTarget = XenForo_Link::buildAdminLink('add-ons');
- return;
- }
-
- if (empty(self::$_config['configured'])) {
- if (!self::$_isBeta
- || XenForo_Application::debugMode()
- ) {
- $enabledAddOnIds = self::_getEnabledAddOnIds(self::$_config['addOnIds']);
-
- if (!empty($enabledAddOnIds)) {
- /** @noinspection HtmlUnknownTarget */
- $message .= sprintf('Updater for %s: enable or disable ?',
- implode(', ', $enabledAddOnIds),
- XenForo_Link::buildAdminLink('add-ons', null, array(
- self::PARAM_ENABLE => self::$_config['apiUrl'],
- )),
- XenForo_Link::buildAdminLink('add-ons', null, array(
- self::PARAM_DISABLE => self::$_config['apiUrl'],
- ))
- );
-
- if (self::$_isBeta) {
- $message .= ' Warning: The Updater is currently in beta.';
- }
- }
- }
- } elseif (self::$_config['configured']) {
- $forceRefresh = (!empty($_GET[self::PARAM_FORCE]) && $_GET[self::PARAM_FORCE] === self::$_config['apiUrl']);
- $data = self::_refreshData(self::$_config['apiUrl'], self::$_config['addOnIds'], $forceRefresh);
-
- if (!empty($data)) {
- $message .= self::_onAddOnShowStatuses($data, $forceRefresh);
- }
-
- }
-
- $paramKey = '_' . md5(self::$_config['apiUrl']);
- $containerParams[$paramKey] = array('message' => $message);
- }
-
- private static function _onAddOnShowStatuses(array $data, $forceRefresh)
- {
- $statuses = '';
- $xenAddOns = XenForo_Application::get('addOns');
-
- foreach ($xenAddOns as $addOnId => $addOnVersionId) {
- if (!in_array($addOnId, self::$_config['addOnIds'], true)) {
- continue;
- }
-
- if (!isset($data[$addOnId])) {
- if ($forceRefresh) {
- $statuses .= sprintf('%1$s cannot be verified with the Updater Server. ', $addOnId);
- }
- continue;
- }
-
- if ($addOnVersionId < $data[$addOnId]['version_id']) {
- $ignoredVersionId = 0;
- if (!empty(self::$_config['ignored'][$addOnId])) {
- $ignoredVersionId = self::$_config['ignored'][$addOnId];
- }
- $isIgnored = $data[$addOnId]['version_id'] <= $ignoredVersionId;
-
- $actions = array();
- if ($forceRefresh
- || !$isIgnored
- ) {
- if (!empty($data[$addOnId]['links']['permalink'])) {
- /** @noinspection HtmlUnknownTarget */
- $actions[] = sprintf('read more ', $data[$addOnId]['links']['permalink']);
- }
-
- if (!empty($data[$addOnId]['links']['content'])) {
- /** @noinspection HtmlUnknownTarget */
- $actions[] = sprintf('update ',
- XenForo_Link::buildAdminLink('full:add-ons/upgrade',
- array('addon_id' => $addOnId),
- array(self::PARAM_DOWNLOAD => self::$_config['apiUrl'])));
- } elseif (!empty($data[$addOnId]['links']['authorize'])) {
- $authorizeUrl = sprintf('%s&redirect_uri=%s',
- $data[$addOnId]['links']['authorize'],
- rawurlencode(XenForo_Link::buildAdminLink('full:add-ons/upgrade',
- array('addon_id' => $addOnId),
- array(self::PARAM_AUTHORIZE => self::$_config['apiUrl'])))
- );
-
- /** @noinspection HtmlUnknownTarget */
- $actions[] = sprintf('authorize an update ', $authorizeUrl);
- }
- }
-
- if (!empty($actions)) {
- $outOfDateMessage = sprintf('%1$s is out of date, latest version is v%3$s (#%4$d, yours is #%2$d).',
- $addOnId,
- $addOnVersionId,
- $data[$addOnId]['version_string'],
- $data[$addOnId]['version_id']);
-
- $ignoreMessage = '';
- if (!$isIgnored) {
- /** @noinspection HtmlUnknownTarget */
- $ignoreMessage = sprintf(' or ignore this version ',
- XenForo_Link::buildAdminLink('full:add-ons/upgrade',
- array('addon_id' => $addOnId),
- array(self::PARAM_IGNORE => self::$_config['apiUrl'])));
- }
-
- $statuses .= sprintf('%1$s You may: %2$s%3$s. ',
- $outOfDateMessage,
- implode(', ', $actions),
- $ignoreMessage);
- }
- } else {
- if ($forceRefresh) {
- if ($addOnVersionId > $data[$addOnId]['version_id']) {
- $statuses .= sprintf('%1$s appears to be even newer than'
- . ' reported from the Updater Server: their v%3$s (#%4$d) vs. your #%2$d.'
- . ' Awesome! ',
- $addOnId,
- $addOnVersionId,
- $data[$addOnId]['version_string'],
- $data[$addOnId]['version_id']);
- } else {
- $statuses .= sprintf('%1$s is up to date. ', $addOnId);
- }
- }
- }
- }
-
- return $statuses;
- }
-
- private static function _onAddOnUpgrade(XenForo_ControllerResponse_View &$controllerResponse)
- {
- if (!empty($_GET[self::PARAM_AUTHORIZE])
- && $_GET[self::PARAM_AUTHORIZE] === self::$_config['apiUrl']
- ) {
- $downloadLink = XenForo_Link::buildAdminLink(
- 'full:add-ons/upgrade',
- $controllerResponse->params['addOn'],
- array(
- self::PARAM_DOWNLOAD => self::$_config['apiUrl']
- )
- );
- $downloadLinkJson = json_encode($downloadLink);
- $accessTokenParamJson = json_encode(self::PARAM_ACCESS_TOKEN);
-
- $js = <<
-var hash = window.location.hash.substr(1);
-var regex = /access_token=(.+?)(&|$)/;
-var match = hash.match(regex);
-if (match) {
- var accessToken = match[1];
- var downloadLink = $downloadLinkJson;
- window.location = downloadLink + '&' + $accessTokenParamJson + '=' + encodeURIComponent(accessToken);
-}
-
-EOF;
- die($js);
- }
-
- if (!empty($_GET[self::PARAM_DOWNLOAD])
- && $_GET[self::PARAM_DOWNLOAD] === self::$_config['apiUrl']
- ) {
- $accessToken = '';
- if (!empty($_GET[self::PARAM_ACCESS_TOKEN])) {
- $accessToken = $_GET[self::PARAM_ACCESS_TOKEN];
- }
-
- $addOnId = $controllerResponse->params['addOn']['addon_id'];
- $data = self::_fetchData(self::$_config['apiUrl'], array($addOnId), $accessToken);
-
- if (!empty($data[$addOnId]['links']['content'])) {
- $tempFile = DevHelper_Helper_ShippableHelper_TempFile::download($data[$addOnId]['links']['content']);
- $xmlPath = self::_updateAddOnFiles($addOnId, $tempFile);
-
- /** @noinspection BadExpressionStatementJS */
- die(sprintf('',
- json_encode(XenForo_Link::buildAdminLink('add-ons/upgrade', array('addon_id' => $addOnId), array(
- self::PARAM_IMPORT_XML => self::$_config['apiUrl'],
- self::PARAM_IMPORT_XML_PATH => $xmlPath
- ))))
- );
- }
- }
-
- if (!empty($_GET[self::PARAM_IMPORT_XML])
- && $_GET[self::PARAM_IMPORT_XML] === self::$_config['apiUrl']
- && !empty($_GET[self::PARAM_IMPORT_XML_PATH])
- ) {
- $addOnId = $controllerResponse->params['addOn']['addon_id'];
- $xmlPath = $_GET[self::PARAM_IMPORT_XML_PATH];
-
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = XenForo_Model::create('XenForo_Model_AddOn');
- $addOnModel->installAddOnXmlFromFile($xmlPath, $addOnId);
-
- $addOn = $addOnModel->getAddOnById($addOnId);
- echo(sprintf('Updated add-on %1$s to v%2$s (#%3$d)',
- $addOn['title'], $addOn['version_string'], $addOn['version_id']));
-
- /** @noinspection BadExpressionStatementJS */
- die(sprintf('',
- json_encode(XenForo_Link::buildAdminLink('full:tools/run-deferred', null, array(
- 'redirect' => XenForo_Link::buildAdminLink('full:add-ons')
- )))));
- }
-
- if (!empty($_GET[self::PARAM_IGNORE])
- && $_GET[self::PARAM_IGNORE] === self::$_config['apiUrl']
- ) {
- $addOnId = $controllerResponse->params['addOn']['addon_id'];
- $data = self::_refreshData(self::$_config['apiUrl'], self::$_config['addOnIds'], false);
- if (!empty($data[$addOnId]['version_id'])) {
- self::$_config['ignored'][$addOnId] = $data[$addOnId]['version_id'];
- self::_saveConfig(self::$_config);
- }
-
- $controllerResponse = new XenForo_ControllerResponse_Redirect();
- $controllerResponse->redirectType = XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED;
- $controllerResponse->redirectTarget = XenForo_Link::buildAdminLink('add-ons');
- }
- }
-
- private static function _updateSelf(array $config)
- {
- $options = XenForo_Application::getOptions();
-
- $configOptionId = DevHelper_Helper_ShippableHelper_Updater::getConfigOptionId($config['apiUrl']);
- $templateTitle = $configOptionId;
- $templateModKey = $configOptionId;
-
- $configVersion = $config['version'];
- $configUpdated = array();
- if ($configVersion === 0) {
- $configUpdated[] = '$config';
- }
-
- if ($configVersion < DevHelper_Helper_ShippableHelper_Updater::$_version) {
- if ($configVersion < 2015100121) {
- /** @var XenForo_DataWriter_AdminTemplate $templateDw */
- $templateDw = XenForo_DataWriter::create('XenForo_DataWriter_AdminTemplate');
-
- /** @var XenForo_Model_AdminTemplate $templateModel */
- $templateModel = XenForo_Model::create('XenForo_Model_AdminTemplate');
- $template = $templateModel->getAdminTemplateByTitle($templateTitle);
- if (!empty($template)) {
- $templateDw->setExistingData($template, true);
- }
-
- $templateDw->bulkSet(array(
- 'title' => $templateTitle,
- 'template' => self::$_template,
- ));
- $templateDw->save();
-
- $configUpdated[] = '$templateDw';
- }
-
- if ($configVersion < 2015100107) {
- /** @var XenForo_DataWriter_AdminTemplateModification $templateModDw */
- $templateModDw = XenForo_DataWriter::create('XenForo_DataWriter_AdminTemplateModification');
-
- /** @var XenForo_Model_AdminTemplateModification $templateModModel */
- $templateModModel = XenForo_Model::create('XenForo_Model_AdminTemplateModification');
- $templateMod = $templateModModel->getModificationByKey($templateModKey);
- if (!empty($templateMod)) {
- $templateModDw->setExistingData($templateMod, true);
- }
-
- $templateModDw->bulkSet(array(
- 'template' => 'content_header',
- 'modification_key' => $templateModKey,
- 'description' => $config['apiUrl'],
- 'action' => 'preg_replace',
- 'find' => '#^.+$#s',
- 'replace' => '$0' . str_replace('md5', md5($config['apiUrl']), self::$_templateMod),
- ));
- $templateModDw->save();
-
- $configUpdated[] = '$templateModDw';
- }
-
- $config['version'] = DevHelper_Helper_ShippableHelper_Updater::$_version;
- $configUpdated[] = '$config["version"]';
- }
-
- $addOnIdsUpdated = false;
- foreach ($GLOBALS[DevHelper_Helper_ShippableHelper_Updater::KEY][$config['apiUrl']]['addOns'] as $addOnId => $enabled) {
- if (!in_array($addOnId, $config['addOnIds'], true)) {
- $config['addOnIds'][] = $addOnId;
- $addOnIdsUpdated = true;
- }
- }
- if ($addOnIdsUpdated) {
- sort($config['addOnIds']);
- $configUpdated[] = '$config["addOnIds"]';
- }
-
- if (count($configUpdated) > 0) {
- /** @var XenForo_DataWriter_Option $optionDw */
- $optionDw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
- if ($configVersion > 0) {
- $optionDw->setExistingData($configOptionId);
- }
- $optionDw->bulkSet(array(
- 'option_id' => $configOptionId,
- 'option_value' => $config,
- 'default_value' => 'a:0:{}',
- 'edit_format' => 'template',
- 'edit_format_params' => $templateTitle,
- 'data_type' => 'array',
- 'sub_options' => '*',
- ));
- $optionDw->setRelations(array(
- 'debug' => 9999,
- ));
- $optionDw->setExtraData(XenForo_DataWriter_Option::DATA_TITLE, 'Add-on Updates'
- . (self::$_isBeta ? ' (BETA)' : ''));
- $optionDw->setExtraData(XenForo_DataWriter_Option::DATA_EXPLAIN, 'Turn on this option if you want to'
- . ' check for add-on updates from '
- . $config['apiUrl'] . ' regularly. If you want to perform update check immediately, click here .');
- $optionDw->save();
- $options->set($configOptionId, $config);
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($apiUrl=%s): $configVersion=%d,'
- . ' self::$_version=%d, $configUpdated=%s',
- __METHOD__, $config['apiUrl'], var_export($configVersion, true),
- var_export(DevHelper_Helper_ShippableHelper_Updater::$_version, true),
- var_export($configUpdated, true)));
- }
- }
-
- self::$_config = $config;
- }
-
- private static function _updateAddOnFiles($addOnId, $tempFile)
- {
- $tempDir = $tempFile . '_extracted';
-
- XenForo_Helper_File::createDirectory($tempDir, false);
- XenForo_Helper_File::makeWritableByFtpUser($tempDir);
-
- $decompress = new Zend_Filter_Decompress(array(
- 'adapter' => 'Zip',
- 'options' => array('target' => $tempDir)
- ));
-
- if (!$decompress->filter($tempFile)) {
- throw new XenForo_Exception('Unable to extract add-on package.', true);
- }
-
- $uploadDir = sprintf('%s/upload', $tempDir);
- if (!is_dir($uploadDir)) {
- throw new XenForo_Exception('Unsupported add-on package (no "upload" directory found).', true);
- }
-
- $xfDir = dirname(XenForo_Autoloader::getInstance()->getRootDir());
- $files = self::_verifyAddOnFiles($uploadDir, $xfDir);
-
- $xmlPath = self::_findXmlPath($addOnId, $tempDir, $xfDir, $files);
-
- self::_moveAddOnFiles($uploadDir, $xfDir, $files);
-
- return $xmlPath;
- }
-
- private static function _verifyAddOnFiles($packageDir, $xfDir, $relativePath = null)
- {
- $files = array();
-
- $dir = sprintf('%s/%s', $packageDir, $relativePath);
- $entries = scandir($dir);
- $subDirs = array();
-
- foreach ($entries as $entry) {
- if (substr($entry, 0, 1) === '.') {
- // ignore hidden files
- continue;
- }
-
- if ($relativePath !== null) {
- $fileRelativePath = sprintf('%s/%s', $relativePath, $entry);
- } else {
- $fileRelativePath = $entry;
- }
-
- if (is_file(sprintf('%s/%s', $packageDir, $fileRelativePath))) {
- $files[] = $fileRelativePath;
-
- $fileSystemPath = sprintf('%s/%s', $xfDir, $fileRelativePath);
- if (is_file($fileSystemPath)) {
- if (!is_writable($fileSystemPath)) {
- throw new XenForo_Exception('File is not writable: ' . $fileSystemPath, true);
- }
- } elseif (is_dir($fileSystemPath)) {
- throw new XenForo_Exception('File/directory conflict: ' . $fileSystemPath, true);
- } else {
- $parentOfFileSystemPath = dirname($fileSystemPath);
- if (is_dir($parentOfFileSystemPath)) {
- if (!is_writable($parentOfFileSystemPath)) {
- throw new XenForo_Exception('Directory is not writable: '
- . $parentOfFileSystemPath, true);
- }
- } else {
- if (!XenForo_Helper_File::createDirectory($parentOfFileSystemPath)) {
- throw new XenForo_Exception('Directory cannot be created: '
- . $parentOfFileSystemPath, true);
- }
- }
- }
- } else {
- $subDirs[] = $fileRelativePath;
- }
- }
-
- foreach ($subDirs as $subDir) {
- $files = array_merge($files, self::_verifyAddOnFiles($packageDir, $xfDir, $subDir));
- }
-
- return $files;
- }
-
- private static function _moveAddOnFiles($packageDir, $xfDir, array $files)
- {
- foreach ($files as $file) {
- $packagePath = sprintf('%s/%s', $packageDir, $file);
- $systemPath = sprintf('%s/%s', $xfDir, $file);
-
- if (!XenForo_Helper_File::safeRename($packagePath, $systemPath)) {
- throw new XenForo_Exception('File cannot be updated: ' . $file);
- }
- }
- }
-
- private static function _findXmlPath($addOnId, $tempDir, $xfDir, array $files)
- {
- foreach ($files as $file) {
- $fileName = basename($file);
- if (preg_match('#^addon-(?.+)\.xml$#', $fileName, $matches)) {
- if ($addOnId === $matches['id']) {
- return sprintf('%s/%s', $xfDir, $file);
- }
- }
- }
-
- $entries = scandir($tempDir);
- foreach ($entries as $entry) {
- if (preg_match('#^addon-(?.+)\.xml$#', $entry, $matches)) {
- if ($addOnId === $matches['id']) {
- return sprintf('%s/%s', $tempDir, $entry);
- }
- }
- }
-
- throw new XenForo_Exception('Unsupported add-on package (no xml found).', true);
- }
-
- public static function _uninstallSelf($apiUrl)
- {
- $configOptionId = DevHelper_Helper_ShippableHelper_Updater::getConfigOptionId($apiUrl);
- $templateTitle = $configOptionId;
- $templateModKey = $configOptionId;
-
- try {
- XenForo_Db::beginTransaction();
-
- /** @var XenForo_Model_AdminTemplate $templateModel */
- $templateModel = XenForo_Model::create('XenForo_Model_AdminTemplate');
- $template = $templateModel->getAdminTemplateByTitle($templateTitle);
- if (!empty($template)) {
- /** @var XenForo_DataWriter_AdminTemplate $templateDw */
- $templateDw = XenForo_DataWriter::create('XenForo_DataWriter_AdminTemplate');
- $templateDw->setExistingData($template, true);
- $templateDw->delete();
- }
-
-
- /** @var XenForo_Model_AdminTemplateModification $templateModModel */
- $templateModModel = XenForo_Model::create('XenForo_Model_AdminTemplateModification');
- $templateMod = $templateModModel->getModificationByKey($templateModKey);
- if (!empty($templateMod)) {
- /** @var XenForo_DataWriter_AdminTemplateModification $templateModDw */
- $templateModDw = XenForo_DataWriter::create('XenForo_DataWriter_AdminTemplateModification');
- $templateModDw->setExistingData($templateMod, true);
- $templateModDw->delete();
- }
-
- /** @var XenForo_DataWriter_Option $optionDw */
- $optionDw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
- $optionDw->setExistingData($configOptionId);
- $optionDw->delete();
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($apiUrl=%s): ok',
- __METHOD__, $apiUrl));
- }
-
- XenForo_Db::commit();
- } catch (XenForo_Exception $e) {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($apiUrl=%s): %s',
- __METHOD__, $apiUrl, $e->__toString()));
- }
-
- XenForo_Db::rollback();
- }
- }
-
- private static function _refreshData($apiUrl, array $addOnIds, $forceRefresh)
- {
- $data = ($forceRefresh ? null : self::_getCache($apiUrl));
- if (empty($data)
- || $data['version'] < DevHelper_Helper_ShippableHelper_Updater::$_version
- || $data['timestamp'] < XenForo_Application::$time - 86400
- ) {
- $data = array(
- 'version' => DevHelper_Helper_ShippableHelper_Updater::$_version,
- 'timestamp' => time(),
- 'json' => self::_fetchData($apiUrl, $addOnIds),
- );
-
- self::_setCache($apiUrl, $data);
- }
-
- return $data['json'];
- }
-
- private static function _fetchData($url, array $addOnIds, $accessToken = '')
- {
- if (empty($addOnIds)) {
- return array();
- }
-
- $url .= sprintf('%s_version=%d', ((strpos($url, '?') === false) ? '?' : '&'),
- DevHelper_Helper_ShippableHelper_Updater::$_version);
- if (!empty($accessToken)) {
- $url .= sprintf('&oauth_token=%s', rawurlencode($accessToken));
- }
- foreach ($addOnIds as $addOnId) {
- $url .= sprintf('&ids[]=%s', rawurlencode($addOnId));
- }
-
- $client = XenForo_Helper_Http::getClient($url);
-
- try {
- $response = $client->request('GET');
-
- $responseStatus = $response->getStatus();
- $responseBody = $response->getBody();
- } catch (Exception $e) {
- $responseStatus = 503;
- $responseBody = $e->getMessage();
- }
-
- $json = null;
- if ($responseStatus === 200) {
- $json = @json_decode($responseBody, true);
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($url=%s): status=%d, json=%s',
- __METHOD__, $url,
- var_export($responseStatus, true), var_export($json, true)));
- }
- } else {
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($url=%s): status=%d, body=%s',
- __METHOD__, $url,
- var_export($responseStatus, true), var_export($responseBody, true)));
- }
- }
-
- return $json;
- }
-
- private static function _getCache($apiUrl)
- {
- /** @var XenForo_Model_DataRegistry $dataRegistryModel */
- $dataRegistryModel = XenForo_Model::create('XenForo_Model_DataRegistry');
- $data = $dataRegistryModel->get(DevHelper_Helper_ShippableHelper_Updater::KEY);
-
- if (!empty($data)
- && isset($data[$apiUrl])
- ) {
- return $data[$apiUrl];
- }
-
- return array();
- }
-
- private static function _setCache($apiUrl, array $thisData)
- {
- /** @var XenForo_Model_DataRegistry $dataRegistryModel */
- $dataRegistryModel = XenForo_Model::create('XenForo_Model_DataRegistry');
- $data = $dataRegistryModel->get(DevHelper_Helper_ShippableHelper_Updater::KEY);
- if (empty($data)) {
- $data = array();
- }
- $data[$apiUrl] = $thisData;
- $dataRegistryModel->set(DevHelper_Helper_ShippableHelper_Updater::KEY, $data);
- return true;
- }
-
- private static function _saveConfig(array $config)
- {
- $configOptionId = DevHelper_Helper_ShippableHelper_Updater::getConfigOptionId($config['apiUrl']);
-
- /** @var XenForo_DataWriter_Option $optionDw */
- $optionDw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
- $optionDw->setExistingData($configOptionId);
- $optionDw->set('option_value', $config);
-
- if ($optionDw->save()) {
- XenForo_Application::getOptions()->set($configOptionId, $config);
-
- if (XenForo_Application::debugMode()) {
- XenForo_Helper_File::log(__CLASS__, sprintf('%s($apiUrl=%s)',
- __METHOD__, $config['apiUrl']));
- }
-
- return true;
- }
-
- return false;
- }
-
- private static function _getEnabledAddOnIds($addOnIds)
- {
- $xenAddOns = XenForo_Application::get('addOns');
- $enabledIds = array();
- foreach ($addOnIds as $configAddOnId) {
- if (isset($xenAddOns[$configAddOnId])) {
- $enabledIds[] = $configAddOnId;
- }
- }
-
- return $enabledIds;
- }
-
- private static $_template = '
-
-
-
-
- {$configAddOnId}
-
-
-
-
-
-
-
- {xen:raw $preparedOption.explain}
- Supported add-ons: {$addOnListStr}
-
-
-
- {xen:raw $editLink}
-
-
-
-
-
-
-
-
- ';
-
- private static $_templateMod = '
-
- {xen:raw $_md5.message}
- ';
-
- private static $_config = null;
-}
\ No newline at end of file
diff --git a/library/DevHelper/Helper/Template.php b/library/DevHelper/Helper/Template.php
deleted file mode 100644
index a6ea331..0000000
--- a/library/DevHelper/Helper/Template.php
+++ /dev/null
@@ -1,56 +0,0 @@
-getClassPrefix() . '_';
- $prefixLength = strlen($prefix);
-
- foreach (self::$_foundXfcpClasses as $clazz => $paths) {
- if (substr($clazz, 0, $prefixLength) !== $prefix) {
- // not prefixed with our add-on ID, ignore it
- continue;
- }
-
- $clazzWithoutPrefix = substr($clazz, $prefixLength);
- $realClazz = $clazzWithoutPrefix;
- $realPath = DevHelper_Generator_File::getClassPath($realClazz);
- if (!file_exists($realPath)) {
- // the real class could not be found, hmm...
- // try to replace 'Extend_' with 'XenForo_' to support our legacy add-ons
- if (substr($realClazz, 0, 7) !== 'Extend_') {
- // no hope
- continue;
- }
-
- $realClazz = 'XenForo_' . substr($realClazz, 7);
- $realPath = DevHelper_Generator_File::getClassPath($realClazz);
- if (!file_exists($realPath)) {
- // not found either... bye!
- continue;
- }
- }
-
- if (self::generateXfcpClass($clazz, $realClazz, $config)) {
- echo "Generated XFCP_{$clazz} ({$realClazz}) \n";
- }
- }
- }
-
- public static function generateOurClass($clazz, DevHelper_Config_Base $config)
- {
- $path = DevHelper_Generator_File::getClassPath($clazz, $config);
- $contents = <<getClassPrefix(), $config->getClassPrefix() . '_DevHelper_XFCP', $clazz);
- $ghostPath = DevHelper_Generator_File::getClassPath($ghostClazz, $config);
- if (file_exists($ghostPath)) {
- // ghost file exists, yay!
- return true;
- }
-
- $ghostContents = "[^\s]+)\s#';
- while (true) {
- if (preg_match($extendsXfcp, $contents, $matches, PREG_OFFSET_CAPTURE, $offset)) {
- $clazz = $matches['clazz'][0];
- $offset = $matches[0][1] + strlen($matches[0][0]);
- self::$_foundXfcpClasses[$clazz][$path] = true;
- } else {
- break;
- }
- }
- }
-}
diff --git a/library/DevHelper/Installer.php b/library/DevHelper/Installer.php
deleted file mode 100644
index 079832c..0000000
--- a/library/DevHelper/Installer.php
+++ /dev/null
@@ -1,107 +0,0 @@
-setupAutoloader($fileDir . '/library');
- XenForo_Application::initialize($fileDir . '/library', $fileDir);
-
- $dependencies = new XenForo_Dependencies_Public();
- $dependencies->preLoadData();
-
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = XenForo_Model::create('XenForo_Model_AddOn');
-
- $devHelperAddOn = $addOnModel->getAddOnById('devHelper');
- if (!empty($devHelperAddOn)) {
- die("DevHelper add-on has already been installed.\n");
- }
-
- $addOnModel->installAddOnXmlFromFile('library/DevHelper/addon-devHelper.xml');
- die("DevHelper add-on has been installed successfully.\n");
-}
-
-class DevHelper_Installer
-{
- /* Start auto-generated lines of code. Change made will be overwriten... */
-
- protected static $_tables = array();
- protected static $_patches = array();
-
- public static function install($existingAddOn, $addOnData)
- {
- $db = XenForo_Application::get('db');
-
- foreach (self::$_tables as $table) {
- $db->query($table['createQuery']);
- }
-
- foreach (self::$_patches as $patch) {
- $tableExisted = $db->fetchOne($patch['showTablesQuery']);
- if (empty($tableExisted)) {
- continue;
- }
-
- $existed = $db->fetchOne($patch['showColumnsQuery']);
- if (empty($existed)) {
- $db->query($patch['alterTableAddColumnQuery']);
- }
- }
-
- self::installCustomized($existingAddOn, $addOnData);
- }
-
- public static function uninstall()
- {
- $db = XenForo_Application::get('db');
-
- foreach (self::$_patches as $patch) {
- $tableExisted = $db->fetchOne($patch['showTablesQuery']);
- if (empty($tableExisted)) {
- continue;
- }
-
- $existed = $db->fetchOne($patch['showColumnsQuery']);
- if (!empty($existed)) {
- $db->query($patch['alterTableDropColumnQuery']);
- }
- }
-
- foreach (self::$_tables as $table) {
- $db->query($table['dropQuery']);
- }
-
- self::uninstallCustomized();
- }
-
- /* End auto-generated lines of code. Feel free to make changes below */
-
- public static function installCustomized($existingAddOn, $addOnData)
- {
- // customized install script goes here
- }
-
- public static function uninstallCustomized()
- {
- // customized uninstall script goes here
- }
-
- public static function checkAddOnVersion()
- {
- if (XenForo_Application::isRegistered('addOns')) {
- $addOns = XenForo_Application::get('addOns');
- $versionId = $addOns['devHelper'];
-
- $xml = file_get_contents(dirname(__FILE__) . '/addon-DevHelper.xml');
- if (preg_match('#version_id="(?\d+)"#', $xml, $matches)) {
- if ($versionId < $matches['id']) {
- return false;
- }
- }
- }
-
- return true;
- }
-}
diff --git a/library/DevHelper/Listener.php b/library/DevHelper/Listener.php
deleted file mode 100755
index 770a5ca..0000000
--- a/library/DevHelper/Listener.php
+++ /dev/null
@@ -1,175 +0,0 @@
- $styleDate) {
- // consider this is a change, start updating the template
- $maxTemplateDate = max($maxTemplateDate, $templateDate);
-
- $templateId = DevHelper_Helper_Template::getTemplateIdFromFilePath($templateFile);
- if (empty($templateId)) {
- continue;
- }
-
- $templateText = file_get_contents($templateFile);
-
- $properties = $propertyModel->keyPropertiesByName($propertyModel->getEffectiveStylePropertiesInStyle(0));
- $propertyChanges = $propertyModel->translateEditorPropertiesToArray(
- $templateText,
- $templateText,
- $properties
- );
-
- /** @var XenForo_DataWriter_Template $dw */
- $dw = XenForo_DataWriter::create('XenForo_DataWriter_Template');
- $dw->setExistingData($templateId);
- $dw->set('template', $templateText);
-
- if ($dw->hasErrors()) {
- throw new XenForo_Exception(implode(', ', $dw->getErrors()));
- }
-
- if ($dw->hasChanges()) {
- $dw->reparseTemplate();
-
- $dw->save();
-
- $propertyModel->saveStylePropertiesInStyleFromTemplate(0, $propertyChanges, $properties);
-
- $templatesUpdated[] = $dw->get('title');
- }
- }
- }
-
- if (!empty($maxTemplateDate)) {
- /** @var XenForo_Model_Style $styleModel */
- $styleModel = XenForo_Model::create('XenForo_Model_Style');
- $styleModel->updateAllStylesLastModifiedDate($maxTemplateDate);
- }
- }
- }
-
- public static function template_hook($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
- {
- switch ($hookName) {
- case 'devhelper_devhelper_helper_addon_unit':
- self::_filterDisabledAddOnOptions($contents);
- break;
- }
- }
-
- protected static function _filterDisabledAddOnOptions(&$html)
- {
- $offset = 0;
-
- $addOns = XenForo_Application::get('addOns');
-
- while (true) {
- if (preg_match(
- '/[^<]+<\/option>/i',
- $html,
- $matches,
- PREG_OFFSET_CAPTURE,
- $offset
- )) {
- $offset = $matches[0][1] + 1;
- $length = strlen($matches[0][0]);
- $addOnId = $matches[1][0];
-
- if (!isset($addOns[$addOnId])) {
- $html = substr_replace($html, '', $offset - 1, $length);
- $offset--;
- }
- } else {
- break;
- }
- }
-
- $groupOffset = 0;
- while (true) {
- if (preg_match(
- '/ \s+<\/optgroup>/i',
- $html,
- $matches,
- PREG_OFFSET_CAPTURE,
- $groupOffset
- )) {
- $groupOffset = $matches[0][1] + 1;
- $length = strlen($matches[0][0]);
-
- $html = substr_replace($html, '', $groupOffset - 1, $length);
- $groupOffset--;
- } else {
- break;
- }
- }
- }
-
- public static function file_health_check(XenForo_ControllerAdmin_Abstract $controller, array &$hashes)
- {
- $hashes += DevHelper_FileSums::getHashes();
- }
-}
diff --git a/library/DevHelper/Model/Config.php b/library/DevHelper/Model/Config.php
deleted file mode 100755
index 1e1ddb3..0000000
--- a/library/DevHelper/Model/Config.php
+++ /dev/null
@@ -1,48 +0,0 @@
-upgradeConfig();
-
- if (!empty($created) OR !empty($upgraded)) {
- $this->saveAddOnConfig($addOn, $config);
- }
-
- return $config;
- }
-
- public function saveAddOnConfig($addOn, DevHelper_Config_Base $config)
- {
- $className = DevHelper_Generator_File::getClassName($addOn['addon_id'], 'DevHelper_Config');
- DevHelper_Generator_File::writeClass($className, $config->outputSelf());
- }
-}
diff --git a/library/DevHelper/Router.php b/library/DevHelper/Router.php
deleted file mode 100644
index 7b9418e..0000000
--- a/library/DevHelper/Router.php
+++ /dev/null
@@ -1,154 +0,0 @@
-setupAutoloader($fileDir . '/xenforo/library');
- }
-
- public static function locateCached($fullPath)
- {
- $key = basename($fullPath) . md5($fullPath);
- $cached = apcu_fetch($key);
- $located = null;
- $success = false;
- if (is_array($cached)
- && $cached['fullPath'] === $fullPath) {
- $located = $cached['located'];
- }
-
- if (empty($located)) {
- $located = static::locate($fullPath, $success);
- }
-
- if ($success) {
- $cacheEntry = array(
- 'fullPath' => $fullPath,
- 'located' => $located,
- );
-
- apcu_store($key, $cacheEntry);
- }
-
- return $located;
- }
-
- public static function locate($fullPath, &$success = null)
- {
- if (file_exists($fullPath)) {
- $success = true;
- return $fullPath;
- }
-
- list($xenforoDir, $addOnPaths) = static::getLocatePaths();
- $shortened = preg_replace(
- '#^' . preg_quote($xenforoDir, '#') . '#',
- '',
- $fullPath
- );
-
- foreach ($addOnPaths as $addOnPath) {
- $candidatePath = $addOnPath . $shortened;
- if (file_exists($candidatePath)) {
- $success = true;
- return $candidatePath;
- }
- }
-
- return $fullPath;
- }
-
- public static function getLocatePaths()
- {
- static $xenforoDir = null;
- static $addOnPaths = null;
-
- if ($addOnPaths === null) {
- $routerPhp = $_SERVER['DEVHELPER_ROUTER_PHP'];
- $routerPhpDir = dirname($routerPhp);
- $xenforoDir = sprintf('%s/xenforo', $routerPhpDir);
- $addOnPaths = array($routerPhpDir);
-
- $txtPath = sprintf('%s/internal_data/addons.txt', $xenforoDir);
- $lines = array();
- if (file_exists($txtPath)) {
- $lines = file($txtPath);
- }
-
- foreach ($lines as $line) {
- $line = trim($line);
- if (strlen($line) === 0) {
- continue;
- }
-
- $addOnPaths[] = sprintf('%s/%s', $routerPhpDir, $line);
- }
- }
-
- return array($xenforoDir, $addOnPaths);
- }
-}
diff --git a/library/DevHelper/ViewAdmin/AddOn/DataManager.php b/library/DevHelper/ViewAdmin/AddOn/DataManager.php
deleted file mode 100755
index db01efd..0000000
--- a/library/DevHelper/ViewAdmin/AddOn/DataManager.php
+++ /dev/null
@@ -1,18 +0,0 @@
-_params['dataClasses'] as &$dataClass) {
- $dataClass['fieldsList'] = implode(', ', array_keys($dataClass['fields']));
- }
-
- if (!empty($this->_params['focusedDataClass'])) {
- $this->_params['focusedDataClass']['sqlCreate'] = DevHelper_Generator_Db::createTable(
- $this->_params['config'],
- $this->_params['focusedDataClass']
- );
- }
- }
-}
diff --git a/library/DevHelper/XenForo/CodeEvent.php b/library/DevHelper/XenForo/CodeEvent.php
deleted file mode 100644
index e108e10..0000000
--- a/library/DevHelper/XenForo/CodeEvent.php
+++ /dev/null
@@ -1,113 +0,0 @@
-getRootDir() . '/library/XenForo/CodeEvent.php');
-$contents = substr($originalContents, strpos($originalContents, 'autoload('DevHelper_Installer');
- }
-
- $addOn = $addOnModel->getAddOnById('devHelper');
- if (!empty($addOn)) {
- /** @var XenForo_DataWriter_AddOn $addOnDw */
- $addOnDw = XenForo_DataWriter::create('XenForo_DataWriter_AddOn');
- $addOnDw->setExistingData($addOn);
- $addOnDw->set('active', 1);
- $addOnDw->save();
-
- $message = 'Re-enabled DevHelper add-on.';
- } else {
- $xmlPath = 'library/DevHelper/addon-devHelper.xml';
- if (!file_exists($xmlPath)) {
- die(sprintf('XML path (%s) does not exist', $xmlPath));
- }
- $addOnModel->installAddOnXmlFromFile($xmlPath);
-
- $message = 'Installed DevHelper add-on.';
- }
-
- /** @var XenForo_FrontController $fc */
- $fc = $args[0];
- $redirect = $fc->getRequest()->getRequestUri();
-
- die(sprintf(
- 'alert(%s);window.location = %s; ',
- json_encode($message),
- json_encode(XenForo_Link::buildAdminLink('full:tools/run-deferred', null, array(
- 'redirect' => $redirect,
- )))
- ));
- }
- }
-
- return parent::fire($event, $args, $hint);
- }
-
- public static $measuredTime = array('events' => array(), 'callbacks' => array());
-
- public static function DevHelper_measureCallbackTime($event, $callback, $args)
- {
- $startTime = microtime(true);
- $result = call_user_func_array($callback, $args);
- $elapsed = microtime(true) - $startTime;
-
- if (!isset(self::$measuredTime['events'][$event])) {
- self::$measuredTime['events'][$event] = array(
- 'count' => 0,
- 'elapsed' => 0,
- );
- }
- self::$measuredTime['events'][$event]['count']++;
- self::$measuredTime['events'][$event]['elapsed'] += $elapsed;
-
- $callbackSafe = $callback;
- if (!is_string($callbackSafe)) {
- $callbackSafe = XenForo_Helper_Php::safeSerialize($callback);
- }
- if (!isset(self::$measuredTime['callbacks'][$callbackSafe])) {
- self::$measuredTime['callbacks'][$callbackSafe] = array(
- 'count' => 0,
- 'elapsed' => 0,
- );
- }
- self::$measuredTime['callbacks'][$callbackSafe]['count']++;
- self::$measuredTime['callbacks'][$callbackSafe]['elapsed'] += $elapsed;
-
- return $result;
- }
-}
-
-eval('abstract class XenForo_CodeEvent extends DevHelper_XenForo_CodeEvent {}');
-
-if (false) {
- class _XenForo_CodeEvent extends XenForo_CodeEvent
- {
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/AddOn.php b/library/DevHelper/XenForo/ControllerAdmin/AddOn.php
deleted file mode 100755
index b6fa361..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/AddOn.php
+++ /dev/null
@@ -1,387 +0,0 @@
-getHelper('DevHelper_ControllerHelper_AddOn');
- $helper->selfCheck();
-
- $response = parent::actionIndex();
-
- if ($response instanceof XenForo_ControllerResponse_View) {
- uasort($response->params['addOns'], create_function(
- '$a, $b',
- 'if ($a["active"] == $b["active"]) { return strcmp($a["title"], $b["title"]); } else { if ($a["active"]) return -1; else return 1; }'
- ));
- }
-
- return $response;
- }
-
- public function actionInstall()
- {
- if (!$this->isConfirmedPost()
- && !empty($_SERVER['DEVHELPER_ROUTER_PHP'])
- ) {
- exec('/usr/local/bin/find-addons.sh');
- apcu_clear_cache();
- }
-
- return parent::actionInstall();
- }
-
- public function actionSave()
- {
- $GLOBALS[DevHelper_Listener::XENFORO_CONTROLLERADMIN_ADDON_SAVE] = $this;
-
- $response = parent::actionSave();
-
- if ($response instanceof XenForo_ControllerResponse_Redirect) {
- if (!empty($this->_DevHelper_actionSave_addOnIdChanged)) {
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('add-ons/file-export', $this->_DevHelper_actionSave_addOnIdChanged)
- );
- }
- }
-
- return $response;
- }
-
- public function DevHelper_actionSave(XenForo_DataWriter_AddOn $addOnDw)
- {
- if ($addOnDw->isUpdate() AND $addOnDw->isChanged('version_id')) {
- $this->_DevHelper_actionSave_addOnIdChanged = $addOnDw->getMergedData();
- }
- }
-
- public function actionDataManager()
- {
- $addOnId = $this->_input->filterSingle('addon_id', XenForo_Input::STRING);
- $addOn = $this->_getAddOnOrError($addOnId);
-
- $config = $this->_getConfigModel()->loadAddOnConfig($addOn);
-
- $viewParams = array(
- 'addOn' => $addOn,
- 'config' => $config,
- 'dataClasses' => $config->getDataClasses(),
- );
-
- $dataClassName = $this->_input->filterSingle('dataClass', XenForo_Input::STRING);
- if (!empty($dataClassName) AND $config->checkDataClassExists($dataClassName)) {
- $viewParams['focusedDataClass'] = $config->getDataClass($dataClassName);
- }
-
- return $this->responseView(
- 'DevHelper_ViewAdmin_AddOn_DataManager',
- 'devhelper_addon_data_manager',
- $viewParams
- );
- }
-
- public function actionGenerateFile()
- {
- $addOnId = $this->_input->filterSingle('addon_id', XenForo_Input::STRING);
- $addOn = $this->_getAddOnOrError($addOnId);
-
- $config = $this->_getConfigModel()->loadAddOnConfig($addOn);
- $dataClassName = $this->_input->filterSingle('dataClass', XenForo_Input::STRING);
- if (!empty($dataClassName) AND $config->checkDataClassExists($dataClassName)) {
- $dataClass = $config->getDataClass($dataClassName);
- $file = $this->_input->filterSingle('file', XenForo_Input::STRING);
-
- switch ($file) {
- case 'data_writer':
- case 'model':
- case 'route_prefix_admin':
- case 'controller_admin':
- case 'installer':
- $suffix = str_replace(' ', '', ucwords(str_replace('_', ' ', $file)));
- return call_user_func(array(
- $this,
- '_actionGenerate' . $suffix
- ), $addOn, $config, $dataClass);
- }
- }
-
- return $this->responseNoPermission();
- }
-
- protected function _actionGenerateDataWriter(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- list($className, $contents) = DevHelper_Generator_Code_DataWriter::generate($addOn, $config, $dataClass);
- $path = DevHelper_Generator_File::writeClass($className, $contents);
-
- $config->updateDataClassFile($dataClass['name'], 'data_writer', $className, $path);
- $this->_getConfigModel()->saveAddOnConfig($addOn, $config);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::RESOURCE_CREATED,
- XenForo_Link::buildAdminLink(
- 'add-ons/data-manager',
- $addOn,
- array('dataClass' => $dataClass['name'])
- )
- );
- }
-
- protected function _actionGenerateModel(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- list($className, $contents) = DevHelper_Generator_Code_Model::generate($addOn, $config, $dataClass);
- $path = DevHelper_Generator_File::writeClass($className, $contents);
-
- $config->updateDataClassFile($dataClass['name'], 'model', $className, $path);
- $this->_getConfigModel()->saveAddOnConfig($addOn, $config);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::RESOURCE_CREATED,
- XenForo_Link::buildAdminLink(
- 'add-ons/data-manager',
- $addOn,
- array('dataClass' => $dataClass['name'])
- )
- );
- }
-
- protected function _actionGenerateRoutePrefixAdmin(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $routePrefix = $this->_input->filterSingle('route_prefix', XenForo_Input::STRING);
- $controller = $this->_input->filterSingle('controller', XenForo_Input::STRING);
- $majorSection = $this->_input->filterSingle('major_section', XenForo_Input::STRING);
-
- if (empty($routePrefix) OR empty($controller)) {
- $viewParams = array(
- 'addOn' => $addOn,
- 'dataClass' => $dataClass,
- 'routePrefix' => $routePrefix ?: DevHelper_Generator_Code_RoutePrefixAdmin::getRoutePrefix(
- $addOn,
- $config,
- $dataClass
- ),
- 'controller' => $controller ?: DevHelper_Generator_Code_ControllerAdmin::getClassName(
- $addOn,
- $config,
- $dataClass
- ),
- 'majorSection' => $majorSection,
- );
-
- return $this->responseView(
- 'DevHelper_ViewAdmin_AddOn_GenerateRoutePrefixAdmin',
- 'devhelper_addon_generate_route_prefix_admin',
- $viewParams
- );
- } else {
- list($className, $contents) = DevHelper_Generator_Code_RoutePrefixAdmin::generate(
- $addOn,
- $config,
- $dataClass,
- array(
- 'routePrefix' => $routePrefix,
- 'controller' => $controller,
- 'majorSection' => $majorSection,
- )
- );
- $path = DevHelper_Generator_File::writeClass($className, $contents);
-
- $config->updateDataClassFile($dataClass['name'], 'route_prefix_admin', $className, $path);
- $this->_getConfigModel()->saveAddOnConfig($addOn, $config);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::RESOURCE_CREATED,
- XenForo_Link::buildAdminLink(
- 'add-ons/data-manager',
- $addOn,
- array('dataClass' => $dataClass['name'])
- )
- );
- }
- }
-
- protected function _actionGenerateControllerAdmin(array $addOn, DevHelper_Config_Base $config, array $dataClass)
- {
- $routePrefix = $this->_input->filterSingle('route_prefix', XenForo_Input::STRING);
- $controller = $this->_input->filterSingle('controller', XenForo_Input::STRING);
-
- if (empty($routePrefix) OR empty($controller)) {
- $viewParams = array(
- 'addOn' => $addOn,
- 'dataClass' => $dataClass,
- 'routePrefix' => $routePrefix ?: DevHelper_Generator_Code_RoutePrefixAdmin::getRoutePrefix(
- $addOn,
- $config,
- $dataClass
- ),
- 'controller' => $controller ?: DevHelper_Generator_Code_ControllerAdmin::getClassName(
- $addOn,
- $config,
- $dataClass
- ),
- );
-
- return $this->responseView(
- 'DevHelper_ViewAdmin_AddOn_GenerateControllerAdmin',
- 'devhelper_addon_generate_controller_admin',
- $viewParams
- );
- } else {
- list($className, $contents) = DevHelper_Generator_Code_ControllerAdmin::generate(
- $addOn,
- $config,
- $dataClass,
- array(
- 'routePrefix' => $routePrefix,
- 'controller' => $controller,
- )
- );
- $path = DevHelper_Generator_File::writeClass($className, $contents);
-
- $config->updateDataClassFile($dataClass['name'], 'controller_admin', $className, $path);
- $this->_getConfigModel()->saveAddOnConfig($addOn, $config);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::RESOURCE_CREATED,
- XenForo_Link::buildAdminLink(
- 'add-ons/data-manager',
- $addOn,
- array('dataClass' => $dataClass['name'])
- )
- );
- }
- }
-
- public function actionGenerateInstaller()
- {
- $addOnId = $this->_input->filterSingle('addon_id', XenForo_Input::STRING);
- $addOn = $this->_getAddOnOrError($addOnId);
-
- $config = $this->_getConfigModel()->loadAddOnConfig($addOn);
-
- list($className, $contents) = DevHelper_Generator_Code_Installer::generate($addOn, $config);
- DevHelper_Generator_File::writeClass($className, $contents, $config);
-
- $dw = XenForo_DataWriter::create('XenForo_DataWriter_AddOn');
- $dw->setExistingData($addOn);
- $dw->set('install_callback_class', $className);
- $dw->set('install_callback_method', 'install');
- $dw->set('uninstall_callback_class', $className);
- $dw->set('uninstall_callback_method', 'uninstall');
- $dw->save();
-
- $xmlPath = DevHelper_Generator_File::getAddOnXmlPath($addOn, null, $config);
- $this->_getAddOnModel()->getAddOnXml($addOn)->save($xmlPath);
-
- if ($this->_input->filterSingle('run', XenForo_Input::UINT)) {
- call_user_func(array(
- $className,
- 'install'
- ), $dw->getMergedData(), $dw->getMergedData());
- }
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::RESOURCE_CREATED,
- XenForo_Link::buildAdminLink('add-ons/data-manager', $addOn)
- );
- }
-
- public function actionFileExport()
- {
- $addOnId = $this->_input->filterSingle('addon_id', XenForo_Input::STRING);
- $addOn = $this->_getAddOnOrError($addOnId);
-
- $config = $this->_getConfigModel()->loadAddOnConfig($addOn);
-
- $exportPath = $config->getExportPath();
-
- if ($exportPath === false) {
- return $this->responseNoPermission();
- }
-
- DevHelper_Helper_Phrase::startLookingForPhraseTitles();
- DevHelper_Helper_Xfcp::startLookingForXfcpClasses();
-
- echo '';
-
- DevHelper_Generator_File::fileExport($addOn, $config, $exportPath);
-
- /** @var XenForo_Model_Template $templateModel */
- $templateModel = $this->getModelFromCache('XenForo_Model_Template');
- $templates = $templateModel->getMasterTemplatesInAddOn($addOn['addon_id']);
- foreach ($templates AS $template) {
- /** @var DevHelper_XenForo_DataWriter_Template $dw */
- $dw = XenForo_DataWriter::create('XenForo_DataWriter_Template');
- $dw->setExistingData($template, true);
-
- if ($dw->DevHelper_saveTemplate()) {
- echo "Saved template {$template['title']}\n";
- }
- }
-
- /** @var XenForo_Model_Phrase $phraseModel */
- $phraseModel = $this->getModelFromCache('XenForo_Model_Phrase');
- $phrases = $phraseModel->getMasterPhrasesInAddOn($addOnId);
- DevHelper_Helper_Phrase::finishLookingForPhraseTitles($phrases, $phraseModel);
- DevHelper_Helper_Xfcp::finishLookingForXfcpClasses($addOn, $config);
-
- echo ' ';
-
- die('Done');
- }
-
- public function actionSwitchContext()
- {
- $addOnId = $this->_input->filterSingle('addon_id', XenForo_Input::STRING);
- $targetAddOn = $this->_getAddOnOrError($addOnId);
-
- $targetAddOnDw = XenForo_DataWriter::create('XenForo_DataWriter_AddOn');
- $targetAddOnDw->setExistingData($targetAddOn, true);
- $targetAddOnDw->set('active', 1);
- $targetAddOnDw->save();
-
- DevHelper_Generator_Code_XenForoConfig::updateConfig('development.default_addon', $targetAddOn['addon_id']);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('add-ons')
- );
- }
-
- public function actionAllOff()
- {
- $addOns = $this->_getAddOnModel()->getAllAddOns();
-
- foreach ($addOns as $addOn) {
- if (empty($addOn['active'])) {
- continue;
- }
-
- if ($addOn['addon_id'] == 'devHelper') {
- continue;
- }
-
- $addOnDw = XenForo_DataWriter::create('XenForo_DataWriter_AddOn');
- $addOnDw->setExistingData($addOn, true);
- $addOnDw->set('active', 0);
- $addOnDw->save();
- }
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('add-ons')
- );
- }
-
- /**
- * @return DevHelper_Model_Config
- */
- protected function _getConfigModel()
- {
- /** @noinspection PhpIncompatibleReturnTypeInspection */
- return $this->getModelFromCache('DevHelper_Model_Config');
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/AdminTemplateModification.php b/library/DevHelper/XenForo/ControllerAdmin/AdminTemplateModification.php
deleted file mode 100644
index 1a75f99..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/AdminTemplateModification.php
+++ /dev/null
@@ -1,21 +0,0 @@
-params['addOns'];
- $groupedModifications = &$response->params['groupedModifications'];
- $modificationCount = &$response->params['modificationCount'];
-
- /** @var DevHelper_ControllerHelper_AddOn $helper */
- $helper = $this->getHelper('DevHelper_ControllerHelper_AddOn');
- $modificationCount -= $helper->filterKeepActiveAddOns($groupedModifications, $addOns);
- }
-
- return $response;
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/CodeEventListener.php b/library/DevHelper/XenForo/ControllerAdmin/CodeEventListener.php
deleted file mode 100755
index 80d492a..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/CodeEventListener.php
+++ /dev/null
@@ -1,69 +0,0 @@
-params['addOns'];
- $listeners = &$response->params['listeners'];
- $totalListeners =& $response->params['totalListeners'];
-
- /** @var DevHelper_ControllerHelper_AddOn $helper */
- $helper = $this->getHelper('DevHelper_ControllerHelper_AddOn');
- $totalListeners -= $helper->filterKeepActiveAddOns($listeners, $addOns);
- }
-
- return $response;
- }
-
- public function actionSave()
- {
- $dwInput = $this->_input->filter(array(
- 'event_id' => XenForo_Input::STRING,
- 'description' => XenForo_Input::STRING,
- 'callback_class' => XenForo_Input::STRING,
- 'callback_method' => XenForo_Input::STRING,
- 'hint' => XenForo_Input::STRING,
- 'addon_id' => XenForo_Input::STRING,
- ));
-
- if (!empty($dwInput['event_id'])
- && empty($dwInput['description'])
- && empty($dwInput['callback_class'])
- && empty($dwInput['callback_class'])
- && !empty($dwInput['hint'])
- && !empty($dwInput['addon_id'])
- ) {
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = $this->getModelFromCache('XenForo_Model_AddOn');
- $addOn = $addOnModel->getAddOnById($dwInput['addon_id']);
-
- /** @var DevHelper_Model_Config $configModel */
- $configModel = $this->getModelFromCache('DevHelper_Model_Config');
- $config = $configModel->loadAddOnConfig($addOn);
-
- if (strpos($dwInput['event_id'], 'load_class') === 0) {
- $classPath = DevHelper_Generator_File::getClassPath($dwInput['hint']);
- if (is_file($classPath)) {
- $method = DevHelper_Generator_Code_Listener::generateLoadClass($dwInput['hint'], $addOn, $config);
- if ($method) {
- $clazz = DevHelper_Generator_Code_Listener::getClassName($addOn, $config);
-
- $this->_request->setParam('description', $dwInput['hint']);
- $this->_request->setParam('callback_class', $clazz);
- $this->_request->setParam('callback_method', $method);
-
- XenForo_DataWriter::create('XenForo_DataWriter_CodeEventListener');
- DevHelper_XenForo_DataWriter_CodeEventListener::DevHelper_markAsGeneratedCallback($clazz, $method);
- }
- }
- }
- }
-
- return parent::actionSave();
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/Home.php b/library/DevHelper/XenForo/ControllerAdmin/Home.php
deleted file mode 100644
index 8200103..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/Home.php
+++ /dev/null
@@ -1,13 +0,0 @@
-getHelper('DevHelper_ControllerHelper_AddOn');
- $helper->selfCheck();
-
- return parent::actionIndex();
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/Permission.php b/library/DevHelper/XenForo/ControllerAdmin/Permission.php
deleted file mode 100644
index 11cf1cb..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/Permission.php
+++ /dev/null
@@ -1,41 +0,0 @@
-params['permissionGroups'];
- $permissionsGrouped = &$response->params['permissionsGrouped'];
- $permissionsUngrouped = &$response->params['permissionsUngrouped'];
- $interfaceGroups = &$response->params['interfaceGroups'];
- $totalPermissions = &$response->params['totalPermissions'];
-
- $removedCount = 0;
-
- /** @var DevHelper_ControllerHelper_AddOn $helper */
- $helper = $this->getHelper('DevHelper_ControllerHelper_AddOn');
- $helper->filterKeepActiveAddOnsDirect($permissionGroups);
- $helper->filterKeepActiveAddOnsDirect($interfaceGroups);
-
- foreach (array_keys($permissionsGrouped) as $interfaceGroupId) {
- if (empty($interfaceGroups[$interfaceGroupId])) {
- $removedCount += count($permissionsGrouped[$interfaceGroupId]);
- unset($permissionsGrouped[$interfaceGroupId]);
- }
- }
-
- foreach ($permissionsGrouped as &$groupPermissions) {
- $removedCount += $helper->filterKeepActiveAddOnsDirect($groupPermissions);
- }
-
- $removedCount += $helper->filterKeepActiveAddOnsDirect($permissionsUngrouped);
-
- $totalPermissions -= $removedCount;
- }
-
- return $response;
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/Phrase.php b/library/DevHelper/XenForo/ControllerAdmin/Phrase.php
deleted file mode 100644
index 596fd01..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/Phrase.php
+++ /dev/null
@@ -1,28 +0,0 @@
-isSuperAdmin()
- && $this->_input->filterSingle('language_id', XenForo_Input::UINT) > 0
- ) {
- return $this->responseError('Please use non-super admin account to create non-master phrase.');
- }
-
- return parent::actionAdd();
- }
-
- public function actionEdit()
- {
- if (XenForo_Application::debugMode()
- && XenForo_Visitor::getInstance()->isSuperAdmin()
- && $this->_input->filterSingle('language_id', XenForo_Input::UINT) > 0
- ) {
- return $this->responseError('Please use non-super admin account to edit non-master phrase.');
- }
-
- return parent::actionEdit();
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/RoutePrefix.php b/library/DevHelper/XenForo/ControllerAdmin/RoutePrefix.php
deleted file mode 100644
index 6e743e9..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/RoutePrefix.php
+++ /dev/null
@@ -1,26 +0,0 @@
-params['publicPrefixes'];
- $adminPrefixes = &$response->params['adminPrefixes'];
- $totalPrefixes = &$response->params['totalPrefixes'];
-
- $removedCount = 0;
-
- /** @var DevHelper_ControllerHelper_AddOn $helper */
- $helper = $this->getHelper('DevHelper_ControllerHelper_AddOn');
- $removedCount += $helper->filterKeepActiveAddOnsDirect($publicPrefixes);
- $removedCount += $helper->filterKeepActiveAddOnsDirect($adminPrefixes);
-
- $totalPrefixes -= $removedCount;
- }
-
- return $response;
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/Template.php b/library/DevHelper/XenForo/ControllerAdmin/Template.php
deleted file mode 100644
index 15725d8..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/Template.php
+++ /dev/null
@@ -1,28 +0,0 @@
-isSuperAdmin()
- && $this->_input->filterSingle('style_id', XenForo_Input::UINT) > 0
- ) {
- return $this->responseError('Please use non-super admin account to create non-master template.');
- }
-
- return parent::actionAdd();
- }
-
- public function actionEdit()
- {
- if (XenForo_Application::debugMode()
- && XenForo_Visitor::getInstance()->isSuperAdmin()
- && $this->_input->filterSingle('style_id', XenForo_Input::UINT) > 0
- ) {
- return $this->responseError('Please use non-super admin account to edit non-master template.');
- }
-
- return parent::actionEdit();
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/TemplateModification.php b/library/DevHelper/XenForo/ControllerAdmin/TemplateModification.php
deleted file mode 100644
index 0c2d53a..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/TemplateModification.php
+++ /dev/null
@@ -1,21 +0,0 @@
-params['addOns'];
- $groupedModifications = &$response->params['groupedModifications'];
- $modificationCount = &$response->params['modificationCount'];
-
- /** @var DevHelper_ControllerHelper_AddOn $helper */
- $helper = $this->getHelper('DevHelper_ControllerHelper_AddOn');
- $modificationCount -= $helper->filterKeepActiveAddOns($groupedModifications, $addOns);
- }
-
- return $response;
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerAdmin/Tools.php b/library/DevHelper/XenForo/ControllerAdmin/Tools.php
deleted file mode 100644
index aace131..0000000
--- a/library/DevHelper/XenForo/ControllerAdmin/Tools.php
+++ /dev/null
@@ -1,190 +0,0 @@
-responseNoPermission();
- }
-
- /** @var XenForo_Model_AddOn $addOnModel */
- $addOnModel = $this->getModelFromCache('XenForo_Model_AddOn');
-
- $addOn = $addOnModel->getAddOnById('devHelper');
- $xmlPath = DevHelper_Generator_File::getAddOnXmlPath($addOn);
-
- $addOnModel->installAddOnXmlFromFile($xmlPath, $addOn['addon_id']);
-
- return $this->responseRedirect(
- XenForo_ControllerResponse_Redirect::SUCCESS,
- XenForo_Link::buildAdminLink('index')
- );
- }
-
- public function actionAddOnsServerFile()
- {
- $q = $this->_input->filterSingle('q', XenForo_Input::STRING);
- $matchedPaths = array();
-
- /** @var XenForo_Application $app */
- $app = XenForo_Application::getInstance();
- $rootDir = $app->getRootDir();
- $scanPath = '';
-
- if (strlen($q) > 0
- && strpos($q, '.') === false
- ) {
- if (strlen($q) < 7) {
- $originalQuery = $q;
- $q = 'library/';
- $matchedPaths[] = 'library';
-
- if (isset($_SERVER['DEVHELPER_ROUTER_PHP'])) {
- $routerPhp = $_SERVER['DEVHELPER_ROUTER_PHP'];
- $routerPhpDir = dirname($routerPhp);
- $matchedPaths[] = sprintf('%s/addons', $routerPhpDir);
-
- list(, $addOnPaths) = DevHelper_Router::getLocatePaths();
- foreach ($addOnPaths as $addOnPath) {
- if (stripos($addOnPath, $originalQuery) !== false) {
- array_unshift($matchedPaths, $addOnPath);
- }
- }
- }
- }
-
- $parts = preg_split('#/#', $q, -1, PREG_SPLIT_NO_EMPTY);
-
- $prefix = '';
- if (count($parts) > 0) {
- $prefix = array_pop($parts);
- }
-
- if (substr($q, 0, 1) === '/') {
- $scanPath = '/' . implode('/', $parts);
- } else {
- $scanPath = rtrim(sprintf('%s/%s', $rootDir, implode('/', $parts)), '/');
- }
-
- $pathWithPrefix = $scanPath . '/' . $prefix;
- if (is_dir($pathWithPrefix)) {
- $scanPath = $pathWithPrefix;
- $prefix = '';
- }
-
- if (is_dir($scanPath)) {
- $contentPaths = glob(sprintf('%s/*', $scanPath));
-
- foreach ($contentPaths as $contentPath) {
- if ($prefix !== ''
- && substr(basename($contentPath), 0, strlen($prefix)) !== $prefix
- ) {
- continue;
- }
-
- if (is_dir($contentPath)) {
- $matchedPaths[] = $contentPath . '/';
- } else {
- $ext = XenForo_Helper_File::getFileExtension($contentPath);
- if ($ext === 'xml') {
- array_unshift($matchedPaths, $contentPath);
- }
- }
- }
- }
-
- $xmlPath = DevHelper_Helper_File::findXml($matchedPaths);
- if (!empty($xmlPath)) {
- array_unshift($matchedPaths, $xmlPath);
- }
- }
-
- $results = array();
- foreach ($matchedPaths as $matchedPath) {
- $relativePath = preg_replace('#^' . preg_quote($app->getRootDir()) . '/#', '', $matchedPath);
- $results[$relativePath] = basename($matchedPath);
-
- if (substr($matchedPath, 0, strlen($scanPath)) === $scanPath) {
- $results[$relativePath] = ltrim(substr_replace($matchedPath, '', 0, strlen($scanPath)), '/');
- }
- }
-
- $view = $this->responseView();
- $view->jsonParams = array(
- 'results' => $results
- );
- return $view;
- }
-
- public function actionCodeEventListenersHint()
- {
- $q = $this->_input->filterSingle('q', XenForo_Input::STRING);
- $classes = array();
-
- /** @var XenForo_Application $app */
- $app = XenForo_Application::getInstance();
- $libraryPath = sprintf('%s/library/', $app->getRootDir());
-
- if (strlen($q) > 0
- && preg_match('/[A-Z]/', $q)
- ) {
- $dirPath = '';
- $pattern = '';
-
- $classPath = DevHelper_Generator_File::getClassPath($q);
- if (is_file($classPath)) {
- $classes[] = $q;
- }
-
- $_dirPath = preg_replace('/\.php$/', '', $classPath);
- if (is_dir($_dirPath)) {
- $dirPath = $_dirPath;
- } else {
- $_parentDirPath = dirname($_dirPath);
- if (is_dir($_parentDirPath)) {
- $dirPath = $_parentDirPath;
- $pattern = basename($_dirPath);
- }
- }
-
- if ($dirPath !== '') {
- $files = scandir($dirPath);
-
- foreach ($files as $file) {
- if (substr($file, 0, 1) === '.') {
- continue;
- }
-
- if ($pattern !== ''
- && strpos($file, $pattern) !== 0
- ) {
- continue;
- }
-
- $filePath = sprintf('%s/%s', $dirPath, $file);
-
- if (is_file($filePath)) {
- $contents = file_get_contents($filePath);
- if (preg_match('/class\s(?.+?)(\sextends|{)/', $contents, $matches)) {
- $classes[] = $matches['class'];
- }
- } elseif (is_dir($filePath)) {
- $classes[] = str_replace('/', '_', str_replace($libraryPath, '', $filePath));
- }
- }
- }
- }
-
- $results = array();
- foreach ($classes as $class) {
- $results[$class] = $class;
- }
-
- $view = $this->responseView();
- $view->jsonParams = array(
- 'results' => $results
- );
- return $view;
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerHelper/Editor.php b/library/DevHelper/XenForo/ControllerHelper/Editor.php
deleted file mode 100644
index 72714a8..0000000
--- a/library/DevHelper/XenForo/ControllerHelper/Editor.php
+++ /dev/null
@@ -1,26 +0,0 @@
-_DevHelper_disableAssertPostOnly = true;
-
- $limit = $this->_input->filterSingle('limit', XenForo_Input::UINT, array('default' => 10));
- for ($i = 0; $i < $limit; $i++) {
- $this->_request->setParam('title', sprintf(
- 'Thread Title %d-%d',
- XenForo_Application::$time,
- $i
- ));
- $this->_request->setParam('message', sprintf(
- 'Thread Body %d-%d',
- XenForo_Application::$time,
- $i
- ));
-
- try {
- $response = $this->actionAddThread();
- } catch (Exception $e) {
- return $this->responseError($e->getMessage());
- }
- }
-
- return $response;
- }
-
- protected function _assertPostOnly()
- {
- if ($this->_DevHelper_disableAssertPostOnly) {
- return;
- }
-
- parent::_assertPostOnly();
- }
-}
diff --git a/library/DevHelper/XenForo/ControllerPublic/Thread.php b/library/DevHelper/XenForo/ControllerPublic/Thread.php
deleted file mode 100644
index 71c41db..0000000
--- a/library/DevHelper/XenForo/ControllerPublic/Thread.php
+++ /dev/null
@@ -1,38 +0,0 @@
-_DevHelper_disableAssertPostOnly = true;
-
- $limit = $this->_input->filterSingle('limit', XenForo_Input::UINT, array('default' => 10));
- for ($i = 0; $i < $limit; $i++) {
- $this->_request->setParam('message', sprintf(
- 'Post Body %d-%d',
- XenForo_Application::$time,
- $i
- ));
-
- try {
- $response = $this->actionAddReply();
- } catch (Exception $e) {
- return $this->responseError($e->getMessage());
- }
- }
-
- return $response;
- }
-
- protected function _assertPostOnly()
- {
- if ($this->_DevHelper_disableAssertPostOnly) {
- return;
- }
-
- parent::_assertPostOnly();
- }
-}
diff --git a/library/DevHelper/XenForo/DataWriter/AddOn.php b/library/DevHelper/XenForo/DataWriter/AddOn.php
deleted file mode 100644
index 397c955..0000000
--- a/library/DevHelper/XenForo/DataWriter/AddOn.php
+++ /dev/null
@@ -1,15 +0,0 @@
-DevHelper_actionSave($this);
- }
-
- parent::_preSave();
- }
-}
diff --git a/library/DevHelper/XenForo/DataWriter/CodeEventListener.php b/library/DevHelper/XenForo/DataWriter/CodeEventListener.php
deleted file mode 100644
index ef74056..0000000
--- a/library/DevHelper/XenForo/DataWriter/CodeEventListener.php
+++ /dev/null
@@ -1,29 +0,0 @@
-get('callback_class')
- && $callback[1] === $this->get('callback_method')
- ) {
- // triggering error for our generated method
- // this may happen if we modified an existing listener which had already been loaded to memory
- // ignore it...
- return;
- }
- }
- }
-
- parent::error($error, $errorKey, $specificError);
- }
-
- public static function DevHelper_markAsGeneratedCallback($clazz, $method)
- {
- self::$_DevHelper_generatedCallbacks[] = array($clazz, $method);
- }
-}
diff --git a/library/DevHelper/XenForo/DataWriter/Template.php b/library/DevHelper/XenForo/DataWriter/Template.php
deleted file mode 100644
index 7690b2c..0000000
--- a/library/DevHelper/XenForo/DataWriter/Template.php
+++ /dev/null
@@ -1,24 +0,0 @@
-getMergedData();
- $filePath = DevHelper_Helper_Template::getTemplateFilePath($template);
- XenForo_Helper_File::createDirectory(dirname($filePath));
-
- return file_put_contents($filePath, $template['template']) > 0;
- }
-
- protected function _postSaveAfterTransaction()
- {
- $this->DevHelper_saveTemplate();
-
- parent::_postSaveAfterTransaction();
- }
-}
diff --git a/library/DevHelper/XenForo/Debug.php b/library/DevHelper/XenForo/Debug.php
deleted file mode 100644
index f7c0b81..0000000
--- a/library/DevHelper/XenForo/Debug.php
+++ /dev/null
@@ -1,52 +0,0 @@
-getRootDir() . '/library/XenForo/Debug.php');
-$contents = substr($originalContents, strpos($originalContents, ' $oneData) {
- $rows[] = sprintf(
- '%s %d %.6f ',
- $label,
- $oneData['count'],
- $oneData['elapsed']
- );
- }
-
- return sprintf('', implode('', $rows));
- }
-}
-
-eval('abstract class XenForo_Debug extends DevHelper_XenForo_Debug {}');
-
-if (false) {
- class _XenForo_Debug extends XenForo_Debug
- {
- }
-}
diff --git a/library/DevHelper/XenForo/Model/AddOn.php b/library/DevHelper/XenForo/Model/AddOn.php
deleted file mode 100755
index e41113e..0000000
--- a/library/DevHelper/XenForo/Model/AddOn.php
+++ /dev/null
@@ -1,34 +0,0 @@
- $groupAddOnNames) {
- $options[$groupId] = array();
-
- foreach ($groupAddOnNames as $key => $addOnName) {
- $options[$groupId][$key] = $addOnName;
- }
- }
- }
-
- return $options;
- }
-}
diff --git a/library/DevHelper/XenForo/Route/PrefixAdmin/Templates.php b/library/DevHelper/XenForo/Route/PrefixAdmin/Templates.php
deleted file mode 100644
index af88dac..0000000
--- a/library/DevHelper/XenForo/Route/PrefixAdmin/Templates.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getAction();
- if (strpos($action, '_') !== false && intval($request->getParam('template_id')) === 0) {
- /** @var XenForo_Model_Template $templateModel */
- $templateModel = XenForo_Model::create('XenForo_Model_Template');
- $templateIds = $templateModel->getTemplateIdInStylesByTitle($action);
- if (!empty($templateIds[0])) {
- $request->setParam('template_id', $templateIds[0]);
- $routeMatch->setAction('edit');
- }
- }
-
- return $routeMatch;
- }
-}
diff --git a/library/DevHelper/XenForo/Template/Abstract.php b/library/DevHelper/XenForo/Template/Abstract.php
deleted file mode 100644
index 41bbb80..0000000
--- a/library/DevHelper/XenForo/Template/Abstract.php
+++ /dev/null
@@ -1,31 +0,0 @@
-getRootDir() . '/library/XenForo/Template/Abstract.php');
-$contents = substr($originalContents, strpos($originalContents, '_params['addOn']) && empty($this->_params['serverFile'])) {
- $addOn = $this->_params['addOn'];
- $this->_params['serverFile'] = DevHelper_Generator_File::getAddOnXmlPath($addOn);
- }
-
- parent::prepareParams();
- }
-}
-
-if (false) {
- class XFCP_DevHelper_XenForo_ViewAdmin_AddOn_Upgrade extends XenForo_ViewAdmin_Base
- {
- }
-}
diff --git a/library/DevHelper/XenForo/ViewRenderer/Json.php b/library/DevHelper/XenForo/ViewRenderer/Json.php
deleted file mode 100644
index d174c54..0000000
--- a/library/DevHelper/XenForo/ViewRenderer/Json.php
+++ /dev/null
@@ -1,71 +0,0 @@
-getRootDir() . '/library/XenForo/ViewRenderer/Json.php');
-$contents = substr($originalContents, strpos($originalContents, 'getProfiler();
- $params['_queryCount'] = $profiler->getTotalNumQueries();
- $params['_totalQueryRunTime'] = 0;
-
- if ($params['_queryCount']) {
- $params['_queries'] = array();
-
- $queries = $profiler->getQueryProfiles();
-
- /** @var Zend_Db_Profiler_Query $query */
- foreach ($queries AS $query) {
- $queryText = $query->getQuery();
- $queryText = preg_replace('#\s+#', ' ', $queryText);
- $queryText = trim($queryText);
-
- foreach ($query->getQueryParams() AS $param) {
- $param = sprintf('{%s}', htmlentities($param));
- $pos = strpos($queryText, '?');
- if ($pos !== false) {
- $queryText = substr_replace($queryText, $param, $pos, 1);
- }
- }
-
- $params['_queries'][] = htmlentities($queryText);
- $params['_totalQueryRunTime'] += $query->getElapsedSecs();
- }
- }
- }
-
- return $params;
- }
-}
-
-eval('class XenForo_ViewRenderer_Json extends DevHelper_XenForo_ViewRenderer_Json {}');
-
-if (false) {
- class _XenForo_ViewRenderer_Json extends XenForo_ViewRenderer_Json
- {
- }
-}
diff --git a/library/DevHelper/addon-devHelper.xml b/library/DevHelper/addon-devHelper.xml
deleted file mode 100644
index da21956..0000000
--- a/library/DevHelper/addon-devHelper.xml
+++ /dev/null
@@ -1,262 +0,0 @@
-
-
-
-
-
-
-
-
- {$addOn.title} - {xen:phrase devhelper_data_manager}
-
-
- {$addOn.title}
-
-
-
-
-
-
-
-
-
-
-
- {xen:phrase devhelper_data_classes}
-
-
-
-
-
-
- {$focusedDataClass.name}
-
-
-
-
-
-
-
- {$fileType}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {xen:phrase devhelper_no_data_classes_have_been_added_yet}
-
-
-
-
-]]>
- {$addOn.title} - {xen:phrase devhelper_data_manager}
-
-
- {$addOn.title}
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]>
- {$addOn.title} - {xen:phrase devhelper_data_manager}
-
-
- {$addOn.title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]>
-
-
-
-
-
-
-
- ]]>
- {xen:phrase devhelper_data_manager}
-{xen:phrase devhelper_file_export}
-
-$0]]>
-
-
-
-
-
-
- ]]>
- {xen:phrase devhelper_data_manager}
-{xen:phrase devhelper_file_export}
-{xen:phrase devhelper_switch_context}
-
-$0]]>
-
-
-
-
-
-
- ]]>
- ]]>
-
-
- ]]>
- ]]>
-
-
-
-
-
-
- #]]>
- $0]]>
-
-
- ]]>
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/xenforo/.gitkeep b/xenforo/.gitkeep
new file mode 100644
index 0000000..e69de29
From 877cb192f46a8bc11595c44f2e228c616e4f8a0e Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Thu, 26 Oct 2017 23:29:46 +0700
Subject: [PATCH 013/118] Add support for `full` add-on files structure
---
dev-scripts/docker/find-addons2.sh | 2 +-
src/addons/DevHelper/Router.php | 12 ++++++++++++
src/addons/DevHelper/XF/AddOn/AddOn.php | 5 +++++
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/dev-scripts/docker/find-addons2.sh b/dev-scripts/docker/find-addons2.sh
index b28adb9..9e5e7d1 100644
--- a/dev-scripts/docker/find-addons2.sh
+++ b/dev-scripts/docker/find-addons2.sh
@@ -6,5 +6,5 @@ cd /var/www/html/
{ \
echo "# Generated by $0"; \
- find addons/ -maxdepth 5 -name .git -type d | sed 's#/.git##'; \
+ find addons/ -maxdepth 5 -name addon.json -type f | sed 's#/addon.json##'; \
} > ./xenforo/internal_data/addons2.txt
diff --git a/src/addons/DevHelper/Router.php b/src/addons/DevHelper/Router.php
index c8ede29..0ae8867 100644
--- a/src/addons/DevHelper/Router.php
+++ b/src/addons/DevHelper/Router.php
@@ -132,6 +132,18 @@ public static function locate($fullPath, &$success = null)
$candidatePaths[] = $parentPath;
}
}
+ } else {
+ // for non-PHP files, we support 2 types of add-on files structure
+ $fullSuffix = '/src/addons/' . $addOnPathSuffix;
+ if (substr($addOnPath, -strlen($fullSuffix)) === $fullSuffix) {
+ // `full` type has js files in addons/AddOnId/js/
+ // and `addon.json` is at addons/AddOnId/src/addons/AddOnId/addon.json
+ $candidatePaths[] = substr($addOnPath, 0, -strlen($fullSuffix)) . $shortened;
+ } else {
+ // `repo` type has js files in addons/AddOnId/_files/js/
+ // and `addon.json` is at addons/AddOnId/addon.json
+ $candidatePaths[] = $addOnPath . '/_files' . $shortened;
+ }
}
foreach ($candidatePaths as $candidatePath) {
diff --git a/src/addons/DevHelper/XF/AddOn/AddOn.php b/src/addons/DevHelper/XF/AddOn/AddOn.php
index 97af710..6631d77 100644
--- a/src/addons/DevHelper/XF/AddOn/AddOn.php
+++ b/src/addons/DevHelper/XF/AddOn/AddOn.php
@@ -15,6 +15,11 @@ public function __construct($addOnOrId)
/** @noinspection PhpUndefinedFieldInspection */
$this->releasesDir = '/var/www/html/xenforo/internal_data/releases/' . $addOnId;
}
+
+ public function passesHealthCheck(&$missing = [], &$inconsistent = [])
+ {
+ return;
+ }
}
if (false) {
From 942f4117a2bd1f528b1f0b4847d76a7e1bea2351 Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 1 Nov 2017 11:18:32 +0700
Subject: [PATCH 014/118] Add support for self-signed https
---
dev-scripts/Dockerfile.php-apache | 9 ++++++--
dev-scripts/docker/ssl-cert-snakeoil.key | 28 ++++++++++++++++++++++++
dev-scripts/docker/ssl-cert-snakeoil.pem | 17 ++++++++++++++
3 files changed, 52 insertions(+), 2 deletions(-)
create mode 100644 dev-scripts/docker/ssl-cert-snakeoil.key
create mode 100644 dev-scripts/docker/ssl-cert-snakeoil.pem
diff --git a/dev-scripts/Dockerfile.php-apache b/dev-scripts/Dockerfile.php-apache
index b7fb859..85b8a31 100644
--- a/dev-scripts/Dockerfile.php-apache
+++ b/dev-scripts/Dockerfile.php-apache
@@ -11,7 +11,12 @@ RUN pecl install apcu \
&& docker-php-ext-enable apcu
COPY docker/* /usr/local/bin/
-RUN chmod +x /usr/local/bin/cmd.sh \
- && chmod +x /usr/local/bin/find-addons2.sh
+RUN chmod +x /usr/local/bin/*.sh
+
+RUN mv /usr/local/bin/ssl-cert-snakeoil.key /etc/ssl/private/ssl-cert-snakeoil.key \
+ && chmod 0640 /etc/ssl/private/ssl-cert-snakeoil.key \
+ && mv /usr/local/bin/ssl-cert-snakeoil.pem /etc/ssl/certs/ssl-cert-snakeoil.pem \
+ && chmod 0644 /etc/ssl/certs/ssl-cert-snakeoil.pem \
+ && a2enmod ssl && a2ensite default-ssl
CMD ["/usr/local/bin/cmd.sh"]
diff --git a/dev-scripts/docker/ssl-cert-snakeoil.key b/dev-scripts/docker/ssl-cert-snakeoil.key
new file mode 100644
index 0000000..d64eda6
--- /dev/null
+++ b/dev-scripts/docker/ssl-cert-snakeoil.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuLUoflUrEZWbb
+2E4vEItF51ho9jjYSaXd7q0Q5yuQvw0vF3a5Ucm9bIWkxJMKBvznWRut+1yKB8kM
+Yi9z+vHpw+A814Mr91WVcwEaH4VdpPAY6iyH3/wfu61poeVifnN5yH02Z/CkC6c0
+/UR9yrE9nGLZ883LTMhPYo/8YoAgFdzZ82aMoMf7ZH8WU3YyWMrvc98hQxsbqcMu
+djXv1301FY9U/S9ybUaJnoRCWOgLGP4IU9rWOfOEs9PtJpax55i3WDTjEPIlvfFq
+bpQAUQDx8+9ZpSpma1iRLx8vytZdTiLxOORIeoYpqWINxNoj6MIGetMMKSKCn1Lu
+kTWqs1qhAgMBAAECggEAOUB9yt/h0aibJMx29pVzkii3MNkuWjnrIYYgsRurgEqs
+ZLxLtCKkM3WGgGB1DKi7XLg3lvq2slBYz48+ldr4xaQzAwy7+ymzogpJZHLT/bIH
+kcj3ljoTv8KK/9vNcJCsGG3EaqvDt8h0z8AoUUjc9al//LBdBuRYO2/kcEtYXjMG
+UOOJdX1n/HDsS3gLnC+TN2m1NFXdt9//AO4lCdbkdAPhc90h44nKOSXl4nZZe3uw
+GNUSmKkY57YZd84Ybyca2DX6hkpBWNSIBbMyU2c2k1Ka5RjVhEVYgyNiCvGsA8Fa
+gGat1KJerS9wOcBRub6qY8mMxtFztgSST8uZGRbcIQKBgQDjTw254uXeK0Xt3Ml3
+Vs7IZUdJK6fP1NFN8lsFCchsZ9aUD+S4zw5o0WBsF8TS399SeczrmhVy3MiLEd4C
+SDTVCMikj2A+VTs7v1ZpqyADh+9xybFq1NOx2dnyB6ochFVAeeEAqE+Pcy6cVmow
+p4/P/9R+tiGshn2mQXPzfzVUBQKBgQDEKWePUlm12+W05SkI9L4LTFgGhZnwTDhr
+t35ZeFM0Ih2qcIkf2t8eVRtVgYqBSg/F4o4efOmoyif5C96Y9h9PicJGYglcDUkc
+XXSGkUHjjHdnZAhxSgSjyXf1QGvwVFtfy5XNAdOB5pVsN6UPhNoAYE0xzCapTiZg
+ZuHgZ+jq7QKBgE/iYHiLfS1vF8xYY5VFNLbIoRzd8sPfjw9NtR84KO+wLoEqdL9e
+RTohGznhwJI7i0h49cN3C0KqxO/a/6LdrXOQ+rKakMBXQ9HjlILeWQnkpAnhUDCz
+SxKd9TJR7z7GYw+dxa1teUh4ulDHLdqbiLcYuZ0udPck7NOY/R79/cfhAoGACnkX
+cVx5M69w3S6a+UJv3xSNYpPBe0XmgUHDui0lbBQiN1ZYy92YqfrrQa5v4aU9oFgP
+NGuA4iUg6zgKA2qok0uMlstnKoX/EEehEAozVV3LhjaeCMZl5wS7pdlxqdwnIL8y
+2UYur7HK6oMyCdkS9pAtrunyanmkrOLxTlTd56UCgYA4qSWFyCj4HhMc/IVUfQO3
+qsqu+2gsNJefR+Wk9iwoNFPWcHHX8SEBT6fa+Yl18OVTbaEtB2Oit+69Snu8/dKA
+icsNheL/djmy2iAd4fDdNmgcZtg5t1Aiu4GmDMZzcydz1O255wxx0Qo+Sb2/ReV9
+Wsdg1FBo2W4107vFpQokNw==
+-----END PRIVATE KEY-----
diff --git a/dev-scripts/docker/ssl-cert-snakeoil.pem b/dev-scripts/docker/ssl-cert-snakeoil.pem
new file mode 100644
index 0000000..c05f2e6
--- /dev/null
+++ b/dev-scripts/docker/ssl-cert-snakeoil.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvjCCAaagAwIBAgIJAL2kworg9DfqMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
+BAMMDGFmMjUzYjViOWFmODAeFw0xNzEwMzAwMzU2MTRaFw0yNzEwMjgwMzU2MTRa
+MBcxFTATBgNVBAMMDGFmMjUzYjViOWFmODCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAK4tSh+VSsRlZtvYTi8Qi0XnWGj2ONhJpd3urRDnK5C/DS8XdrlR
+yb1shaTEkwoG/OdZG637XIoHyQxiL3P68enD4DzXgyv3VZVzARofhV2k8BjqLIff
+/B+7rWmh5WJ+c3nIfTZn8KQLpzT9RH3KsT2cYtnzzctMyE9ij/xigCAV3NnzZoyg
+x/tkfxZTdjJYyu9z3yFDGxupwy52Ne/XfTUVj1T9L3JtRomehEJY6AsY/ghT2tY5
+84Sz0+0mlrHnmLdYNOMQ8iW98WpulABRAPHz71mlKmZrWJEvHy/K1l1OIvE45Eh6
+himpYg3E2iPowgZ60wwpIoKfUu6RNaqzWqECAwEAAaMNMAswCQYDVR0TBAIwADAN
+BgkqhkiG9w0BAQsFAAOCAQEAVPMo4zeyMy3Sky+pmrxAc6ORIRpG0eKDw+8Uo+Vn
+nrzJ/wChyJbJ3tpIX2CWn3BvA2T/DHuCr4FTcBQHWWo6alhWagAnQ5i+RMVPP+8A
+6ZES46lhRX3jxngkQ4iuQCeOtT/1qpvHmp4U1jZ3mGoopEvbMd7kn1ByS7DxsFxQ
+RMEw1JvjSESzncnQJ0CoyxCqMd5UE749jcitvHNEhI5SU4smjdwy60NHP2xgBfxf
+YebDtYQkIrp/6usr+1E+wGw1WkoJyoBrIlX7Mfgzv2R3UEiMJ3oEqu1F6tgs6AQo
+Qg+2fXkrqtySheVEg0ebKTambjbzWxv5VCUo74q56rDKMw==
+-----END CERTIFICATE-----
From fb18ad4cdecdef547a3c1131cfbf782c4e93abde Mon Sep 17 00:00:00 2001
From: Dao Hoang Son
Date: Wed, 1 Nov 2017 11:18:40 +0700
Subject: [PATCH 015/118] Add support for composer.phar
---
dev-scripts/docker/composer.phar | Bin 0 -> 1852323 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100755 dev-scripts/docker/composer.phar
diff --git a/dev-scripts/docker/composer.phar b/dev-scripts/docker/composer.phar
new file mode 100755
index 0000000000000000000000000000000000000000..508f9cf274be7a7526630f9ef01c183e6b17612f
GIT binary patch
literal 1852323
zcmdqK3w&Hhbw3^mPeFKvHz8RY*}JkWt@94DtwfP!TVPv?Bs(FNBi7PN+GMq>?5=Dp
zi31I!0a9KKk3hpOyuz!c5Gb$mD3C&UrRCMakCu0V@`IFy5}<{`|NA|2&fK|o@2+G!
z^!NMxU7Dl4cjnBQGiT16Ip@sWr>)8_)@%6#rAoe7Iha|PTjq9}ncbywJ<~s3tj!lHmCWW!!TouG{n0aB
z>|{USQk12)Rco2XTro4VST1L#stZfC
z((GI#Q>aX5%B87dr4G6(GgUO&C{-&PG7IHm0nZMWiicR8RjpiOuFzm7%GIetqd1+x
zFTOrdZ8WO$>{Y|M%5LR5I`a8U|G{c$I@3QgkeMnh)=QPyObtz=_)MWuC})bbTD1nY
z6lEcXbvE0aZb!D#Z
zAeY(1!D3AsAT5PjF^7TGN7~qiO!my4jXkHIk;PwqTcX;60aVnDil&A(WHxqfl9rBS
zie+%!HOq=KhiaupF*i28b#QbPh%YYGD&StPuyXU3jB7G8Usw{xVthk>9-1pwGPT7D
zt6Xt2AR!m$Uub9i41rfaFS2?B-yJ>qng7b`^MwWek;@vPJq!4m1#PwBm5ZfXF_Yy3h(@e{
zOgMXz-PF-BIk}^M=lJBn@UD@eor9yfu1%Tq`%daO^zsu<`0@QtIN{MJoN&VZPB;nw
zwMxtwd*j}s@YlTL&hB^xmJaEDZfA{A1Ka21qQ+I#+
z{lLzDto?pnX9%(_5!9yUN(YMpik**jCc
zg-|C!{m(ws)hB%Q$f`G4{hoI9w-y(QmFZ$-YAM9@M8yQS_3HP0(J-xRg=qk)ttQM2
zuIFg!?Ly|mTG0DG#g-e@D7K8@R4`?@PUS*yCsHN5mRfVR=vD96^_nN
z&vJ+ePyWRdUu=k4j*eqXJ)q?SI>3b0+duSQh9cLBmaV12Yy~S(fa9)hjts*04EAj^
z92;8VSe#$5i4x#>n~pT$Z2lGN4Npr+89ZE^3hI|0?C2nTz~JwmYxP@>%=U8iK%pF9
z_^o0f{N|3AP8f!k6zr(ZhnzU=5sm`FsgFGV_GRl2RZ8K2j`z9xgpYsx@UN|YwiPFb
zDs`-iVHf^er##`tU-rJxP_!J-oo-PNg<`FCgYYW{pLgoAIEEJ+RQ}TeE8pGWxjNLt1<0!KPX4)Ov2X>pY<@q(2}F0
zZV3r`^19O;3c?Hj^K*9^ik3YYU95~v)k+KD$iDvJ4hi9j+yC@AhNNi;(1!C4Lw~tc
z2pMvsDj&i-p7D&|8k&|28G{C!nhSdK{f9V02=95=M-CZ=mP&A}Se|ifdw}I<8yyzH
z-@Nn0hZ~lb6J>0!dMK!W{j{r3_#Zc{`?1w;-H%40u^4h9|2T(%@E3!RnKTS7<$P>$
zcD7iLiPzh)xKlU?U-j2t+haIdQqjbRt5-V=gvA%X^Mi(=B^7%M6p90!dh2
zxFv5mo|H0E+#Uc6^M9(YB>cphKfci_J)yZ0S0AqBpHf;0w|(k$Z?GyU%MPo+xFHyW
zQ8rZ^sx*qVnZlG<6_2>T>onneAHL5MoYHAQ^o)A75+Wlpt5i)9er)Wd-9}7`09ESM
zaxve(uz+oa>^WHfU(Rqe5WfB$XK%6kDQk|azoRi&SY_o*pd0l7x
z0}?r)VnFy;6Zft&%tJ?oc~_x!g=jf#qBtFE{R?I{*
z|EEzx
zeB!M)jvBfqU6dl;Bh_k|e0=w6$4A1aKJ+DP3`f)2oQ9)Tg`;4eXkMeH2H}Z+b;I8t
z4H{YZ5dL8A$ny=&N_u3I{{6bsA^gVGFZLUQl_(?PIAbS#3AI95G}~L1
zAi`_EnEkmCv=a6Z*;rBf^$s-%2`B&Vx~CW#>|K^sM2aT18ya;8f_vi#9{K+eya+4uzG5}Mg-6m$q
zvXqX)^;j=V8HK+%+4Y%l>SyEsY^a)Th3cV-_!n#We&-L_>YuQyAJqL7;qC8!)PA%p-Iuh3glj)>i|=5wz((k{!F?W~
zBhP=TCKcgXr~dkWMpV205lelk-YCw;M5;skN7&PSs<*wHY|n^-{I+VnUMfcqNYb+(
z=0=Y2_d{>@wrA7EGOCu}UTQ?Yh~#zPDu91TnWmvG$Oz7c=k`Zij
zM+#F{K$Zs&H)@!%&hNUYO9bIn&wtzvhO4J7t{Q?HQwiz0L$wIuDKG!be;cN@eS;4=
z-nSp9Bolt?v#0&Yu(TWS(Ik@prKAv^^UyQ>rnMcCv4fR3CEJzDgnxYZ-dC-JlJO)Z
z-`DOC-u0)KoNq|lu~$?oby#dhZBc8J@Y28g%(o2F(}6kW*^YXV-#b+wC@e5c6FrQn
zp|2`=gwKBKFMerA+c64ABW#>rtJSC@yyeNaE*O$_6XTMr;y6MB>f+8#-!&ZV2)Q&l
zLvO~S!+B2lXBXVRwdZH>V6oEJTB3C!!bd~OrnBYXHUY?U}*gxl_Tr;j!~
zIaz0DIM0yB&@fy2lSjE)gt_a=FSnLXNvpLuGXoY%y>)7&6Ta~;=fUGeZtQ@;m`ao9?s5q*FfgbaBPD8RGpE8C;Y?f9${^!^>8<$$&S*GE2V_no?ZW3
z>sq=~7eCFB*E+rso;UKV)(Y6%2(|o$rAnc;1sGV*;b#WGSW
z))7sL`@uesZgYeQKYsRW-)7jF^k0Nc{}QK4V0?9}aYFd9BY$$PVQLc6CQQYepWlzr
zHJq^g)vHQ|q-l+6g+z9H-_lMI{`A%hwi}ivBR2(0Ogg@=>W1*L=iTJTc|8!vbb5qn
zXX!vKIb@gVkP$xW$#>O^o+i63LbI#bz``KrfPcu*zeOdF@Y9d^)G9;Oq|1FRsDkQ(
z|3w_@Rptpl{()Ub3`e$gugo%`UM7m7ZomjPy*u+{L(zte5QU7=2h{{6oIddNy5VRe
zdNh(!ro{Ii?u3|d)2~b3oM=Z4FvXbiMm0$YKXcvtzhu<3k-U-e;%uoB!*Z+2FX86K
zfB8Lg8-dY~XbjC$6%FCKjTej?-wf-&Exnn?yL;+c2zNK@U@Zs-y&xZ^pY@E2p
zYC&Z&(CF{hX+=2uyDxVct}|DPE54!4l46rrJ_Z+
z^!_cMHtLpV9^zIKgK9!~no>x3|EaTpaTC)C=LoN6I_O7^sGTKy)iZG>>#%0(oRxOI^@eyg1!{N1k4zu$1Q
z`%RSBX_p8e@#AZ~o9FyiW0XJ{
z{~6=fw4x?_^DlOKSIJ7L-G!{fQfq4eRuj!s?S*4nqPa#acYUv#fmfSFdbC<+ezXR*O#vxHN3
zywoQpHtm+IS%opqzFy5b!r$C^nO_21A|XqYcFk!Ib(9c3bn>}AU#q1W55|Q(d9-RR
z!u!AE?iNQ6L)22_bYj{B-HzxZv&%u#=iq7}cL_gx^~vut
zR4rG_1geojO)>pii6T7pV`us}KudcnfeD#M$ai$E+-IsiMfjf0kNk{L)yyf`Smebu
zSSptqOTnM)-o+3wc8~CflW#lS5J7|!MqV=_mlumo$H9`CjIUN~gufa7UY}uWo&fx-_RwexOm5*C*Rhwl;tGd?xcK>^-s|erw={ql8uD;uGNp;ymep1^fyzH*uJ;>^3
z@jJFZcNO!tV}l84i9o+l6og&bbze6WYucg+tmM=lLNRGpWE*`D|Qt97hth2loB
zUV@A+IZGz`O*AJz!gY`E$6siiU}&1AQdA9S8YS?z5j`TN53BeSzTlg`+H9Db7N)40
zI2EIx#4@Q?BjKxl^KI|2Ye$b6{BcYZtD3#{}??@`X;KT$?`}nh@O$CO%HLX
z2;cm!vC|AyQ`|SIrm1O*HSP$Aw1#Qd*5qVnpT6TTAq$HnOqFXSBmA)BCS)>
zG^A>SaOxrFcx+ATEQw7q4dGlB@%*hCkc7SC>pp8#wVS6xRTD*b>mHBr{LehghXvY6
zZX8p+ar(xnho4Y`hwuTz&-G^8QQJzv|rSP4HzU#G9~A
zLf);?Pk8G|AAGoVb6Gs2u&EeqQc3$$?IU6JrWf5|c-qb)$bU2(l-GA?uL!$teclnn
z(pFyKrsOHYIHvn)#GdfP$z4A+OzqS|gdvE&y%K?Kst3a7KJTr5bJ$J}B1ADI_)p3s
z!asfV_daoSGzHeq230nMAG?0ym4@itHrh0zX$;X#9PwLeQ^JLR`15xd%68pE
zj2ls{#(_ob#Tr{{IIShh>leD6CVar*H~Ro{JCPzP&1~^_GLLbbC)|JIXD>36+No6H
zfg3P|s{f-ZmGF+2eE6}3t*Z^s6kF7_ud6T+eslG>Un`dD8c5o)cp$=a73@C>72(OJ
zeZwcctpLx?LV)L{)eaBgx>w)wbnDdV%XDhTYzl||ML9%R`m^7D$B;F-0wYmk?nEBx
zJATZHI%b5!tA@RS({2J2#|ZWUk}Tsf4XZ*Yyx{ZyvD>IwZoHVG;*mT~wE*E$|KTd1
zsjwUpkQ5>Lq0TnK;@95QZImo0haO2x350p*cV4pZg@$bzk|Lf{PyUroF~a-3;~#x&
zwaJSgRm)$DIZrb;#f*HUE&_xf+x~=gMpHX|V(6k4V%>b9>N>)g=j)#H*q_CXNSAU!
zn|>ZgMg2zXVwB?yA$-%`OB+T~lmAxh`-<<=b(^rtQr7}+#&+RCd;XJ2EeO_gNuH2riThq{6DfMj?Akn8ny^^zp
z?|jhNKKkCGrW%2)`CE)5YZBJqIrAfHb4_}iwdxdHmM-VZiPOW?kwEx@uKFQEur?I|
zPl`5NP+4~TB;mGcM*`u7fg^sqn@;=~!u&`HlDY76WjJBaOP=E+wq33dhb^cw6CH$l9U-YViseiHRL7mB)T$6-v-tnk=t;usx2P@8WwdPyR*C7kt
zIQPekRqAsYaCmP
zbvb$t5erVt+EYDh^KGS#@O^*sZXdU!Z?px!3^R}=7d`lW*6~2x>j|-lsFED4=qHK%sXD$1zk6QypBO&6a+W8OM^vYA+8~be
zLyvGY5?*m@Pr-1mjK*;&?>N4jpg7rQ!VmBN`(GPAZVr~G@sjy+9Ni;fUJxDO18yAY
zG<0pq6Tzczo!P-UDZGLG9$lmeFTLW0XBa+uW?GOJ!{-rwN|#E)FCYG=2}2Yp$K=??
z5t(N67#{=Mmt7_N?&Ht+@zG)|>J@6>bzg#;cS@!uM|Yx1EL&soX6)Yh;;X+Fiv-
z^F{29>;(n!tI9>f&-^Om5p&Ddii=i1?=i*Z3N*O%p70I*7kU@aifGkbi2mB_;ykuO
zz6H|y7DY_>k6-xcrN)F!_`NKxig;L#_q9ZO^5?&{!_c;ob6-EdvpQRv@;^!CpDLAv
zcMVSz4IMqm%TkG(IvS;U=Ep@jAg@w{g#Y{Q4d)obHWSC+D3DLNVc_AaCkUVS-L)4Q
zzBc325?^fK|3qbp@aOm3^KnDmM)S5rJl0rpQR$tJa?(WjqL-bw*^pk?nw{;D#z_8S
zC7JM>qu=nBU*EA}A8R0=jbgA_)
zlT#0H!%cY8fB(66UF4QU77_FpY5@=q|8xC~*0tpcayViV@*^}%T~7G=C*JveL&nH^
z``$TZdf5DnN)ut>1=o6a(DH*fMB}#q5N_^^dN`$GOL)((Uv`ZVm0y-9Y^=FWjZl3F
zeo^+1@PgaF|3`+Z%`)mL=YeV#+k^Ur1=(@d*EmFkKYrI+Jfb!>Tmn%<)P(w52_JL*
zjo&q*+Nk6NuA#~_FK5wQoyR)To(XvODR;fw&@E4zwfq|W7P7(AZQ{kovf}R!nZ8U-(ZNA=eQsmE8?a~8@;>02KJ5cqHh=8W4Jb~j6HrO
zIZfG2IP$Icy~Xf=Wvyni%sphl%pR1#$mz$blalaV|NN+13`-kM8x|xMdo+_u4B@`_
z{kxB-uU(E9kKr8;b8I0z@yVyX-w0`=z#Sp-Uo)`SrN=zTVIus*KR@ARhN+E#5n`fh
zim6PTh?4;$yy5ckTMb{E!4L3lEzaP&O}paYRqz&Z@Ci2#mwjq(8%-DBijj4z3OC_x
zqv!mSk<~_y0#wd$B)d*g*(SWG%iRWfzJ7MOj-*~g(!7|!P9(N86
z4DKEq?9xS&@B>|g&$gOvm;yT$_bS+4%Kah{n^pZ2_I~fqvY~1-R?ei%Unqw?acz;@
zVMSaAbX6k!QUCdVg0&$_ap@K>LiIjfE(tr{I5uHqwb>u1p^A~ZA8}JKG4lJ!h4^5w@(OYW2M+~hWkLOa2ncA%EOZCIKrMsPWZrkn?bbd
z6x7s2$RF!QnDBW|xafiv45G(re~`APDQ$$scV&F+B-48Gjm_yQ`Qt}AE)jlo=C?0j
z0S&SU>1NC?NR!HK!Uz23f!?XqX1+K)^52*Ud{sq&@b%yR+~r16n}u6ZIitkW^bt7P
zsc#8?`KggvL)6Cd8b<~z4?{%v;khSd^H;Ttgn#kVH%}Y7HeFP7>h6h9y-U?4;nnA@
z{*|FhnRepeR|N%)8)>_SYAC`jC(Qk`;Yd-r4u|Z;*pX1~K7P1xzSZn%mY1Mr!t?(#
zM4ic8!f$VW>;Ew%Yg!>mh8Uka;1Cer_QQMqagdZ9wxhy35?l}>)dlo?WeQ>CWpB9N
zs7jfpO-NnPN67R|kzF?{PQvbvY44m%(QiqdID0i8&nddUA|vcx{TnYtDO>3zvTeLm
zyqUb8sVoux@ZmF8S(jIeaeS^;Jw)4EP_x5ZRWA{~;o+aW+c2ignh
z@ou0Acl`WIk2G{CyNo0{X!GHjT?i)D;#|BILPp^u{K?y@-!qgcZl5H|JvfjGqpUc+
zZLuPkPsM0`fesJh8@FFqF~pl%O0{{n@*Xix$j?>dp2^TP^R}Je=ry!0XPhT0i0qvP
z{+%b06axA^g(i`7PGS;L00m!W+}>VLbM4KXl<<
zYhc&P8}OXAA#7@qJz0cniR>c$@#QBz$r^g*F*M|cG_{%Cx+)OfS?%_bbwmb|mah}V
zW$fPG=6W;!rmV=pspy&vn``f2|$>`j?!rlkyV*P)M_MhAs!S1XefcJz$egm`K$Xeq&FR8|7%m4G
z+n;LmyWi~2gzl{gKYi0@pKIMo@!_{Xwhe6r2w$$-Si(Z}EuS%jDTZ|tp`$7`Vz;Zc
zNBA!v{@hx_ma=I}V7stBouKRt-DweCl)rOe<+!$278AH0r??1DdcqsLd6KfjO3*bj
zw-9ja7mA1Q@lWmb##V~esCZy4s>g`YuE2TpC(0_q#~=EgKW~sa2SqHR`uVNyUhDwf
z8bwFA^KS<4G;XyPi~!y4#d+K?6+NJ&U8@KQUvWd`upvyb=(MX|C*=F5=*r!S{D)$E
z>3qJ
z!Y3WP&d=x85;}NzfzEOs^^5+b1Uy)6QNs6Czxp8KL8|ss9(YvzU<8qr;0cm74Df;%
zJ>{nMukgv2$gI2v5Cv=C7Y&}aOtcVWJwLtst
z%5}nTZ+y)I3~hf)u6wkYT)e1cE52C7W6n#>H2+MQOZc%Df7x#$Qe4GJ<^mZm`)JJ7
ztvPZwqry)(bJ2j0TeMbxR$I2!>}D|GL>DI|RD`uBaykfoZk@t(^K
z9=MTZWJ-=SClKd#+zCH?=V{L}#I4soPbJ5^nU{aB-6s6&Q{Leh(iKoT%5z6`oD%YK
zYHd-X9`{hEI0%2*c$qi4S1?z{N>>$IOsZLBEa9rJzS?K>wAPG9Q+^D0i_%qK4@l$&
z6??*0ZM^psW9L&_&e3H|)@;QH5C+iR9V;oYALy4Dar@heBX
zi?9uXg)%3Wo$>p{4W8$KS$w;X*^Wr{@XZG-57jW=AukQG9uWUn8mK7c2aVN+Dma9)gL71C7LeM!&;N
z_|ZpidE0T~UR=N#3&Z^e#ZCB#Z#?ua!=2*vZ>Bqz(0seD`h?fK=e
zMjPqqub5po1Ll#bg!_Nl=dJFP+1-q?T?*LS6fxl!KfZp_Xib@i&4}R#PULF6LeUXs
z-uwM~3|-0++>CC-F@xq|!ty+tznod2!o`>Gy
z^CD8za5KGWxn(yi0|?*vl&^ay+j{&?WVb{Jd8;ioT4b&LnbJl0-`CB17fOn?W@u98
zaTv->6Jr&=k+}tT$|6p~tkOXE;vc=w+tDd%$!Gwk5r>q_{jH7~;p7uu{d5zCcBGUR
zF!%e_fiS{f#B`bNUkTULulI-6@~yclb@TGy*i71bKR1&I4}SgM-e5$f81}wbLKMED
za@Z5}10{&?exJF`+k|P_(zq;GE`~u~JyiFIUaX4;;q`xc>YS0Yx=qg_4!?EOb%t>L
zCvH2fwCev}Aze`n5)W>!SN=R1s(OVpDsa
z4m9DDUb)~^Q8=}i!Ocs*M*L+*dH9y7g9+dH?VEpToeZ5s%OKo|BNdX{6>bdI{QgXz
zl^5#CWpI%&tlzOdpQ?Q(eBpZ@<@+4&CYC|#{o0&&eHaIJneg{l-g>KbIaEW$7aSr<
z8QB-ybH3k)tWAt;P?INPiiZol#gJ`hwJ(H2r#$dY)^=#rB-)l^oaGW!5RS7D$AHQu
z;TMm**dO-_a9oZpYN^620uNLViGb?aAd}II$|&Jq|L8$)H#!0>)*8n&!bW-iht%Ao
zJtCZW%457|aCgL$)|l8g%HxP5XB_X&^^bMZ
zKzP&mE#I*29B*6Z?hRS@T3x&dA9w0i-gAGvZ4DoAU*Zkhy5rHVUxaV})JME)`FPuM
zQ##JW-8zE_FZ#;7k5C?WTe9hkxAh#=aD=xVdhHE1UY8uFY|xoBFjt(q0*5;AJz@Wo
z8mgde51ki#58S`((n$)K2qv8%^A2jr!(dnk2k);imVk_sK-Z
zt4XA_m?l}PTTjBXZhpJpCmyeg^j1M!avrhD%?QGmefj;D+9+;228IsepbqYG6q~CG
z%gz}}|EeoE;e)Q;{VHqWSmuE@m0E1Fhd?#09l|x&JjrLZ?>&~@_>_!xyYE+Oi-b3R
zXS?@UAImx&^-f#FP~koX|CqC`tnCr@uiE)FOBj+xyoue)T(RZ$H}C-SCPn
z9*-Co<;neXuDxBw=~96c!M|rm7qz2=TjqZBU~Bl|`=nuUQSGUfVoe^>CJA?U|9qb{
zc`Q@UBi56yQ`PCDssAn2WrPzSJgLK)JH8&8|1CBBqN+V4T)6w6{?nQ~7Bv?2FwKi{
zy|zU7kTuu8+FH5{zmHvR_EfONz(-c;<_Oe`CxwFZTy2?fvHNMKTg%vBA5Y7*5;@Cn
z3ddTzTw5dj!IS%5YOPT&?L9EU?mJjm>Cdgn#ko=ljX{%wuTGeD8T@C)rPO@OKY%
zT9k0tOP_zQ^(oleu6WLm&P<(o=9y0?+SjYR6V9CVtM?k({$rrtX^t^OF*D$P*!OXLA*{Y={SoWJv1ngtpZsVMCQcDD6?x43BlhaMpq}j!
z?)zQ;A6a|HvaLw8H?UZf^G&y@8Atflx9;>2>0_BLi6$7}*k0rXz6>6Un`w=T7U9{e
zAN6|c(0$O_NUf+TnQ{#m*|$L@j_~wXp6%z$v6#h)zTu}IshEiTr4AY4?{ECX53Gj+
z_?DrxBmZ{Y8;$%O$A;f@W{gG8
z11?H6;bWfhT<`Ow)piUlN*{;owGqO*fA|#dz6t^kD^_B$8?h=X!h|pT(lu{24y}o(
zpMv)a+of9K;uv+TouV_2@Ie>Oc~=XZlGgne2*h_9-Ag1h>PNbzCH%mv_V^hUIQSxL
z#d+u&bBnd2qbrRLS5w?`v1GFQYi9@#m7nMR`axnrvU!KZw&gxl5Z{-4O{Xs5z5mwt
z665k&%Ms`C#__#F*9XEohtKsVCIfL;hRz6IOqjn7f+);{8wWP{t#^k6y_G7Zw
zeP1MU!~e}X$1=i`yXHS_gse)zGgat5u*m!5(xi6Xs>_~jm4T~i_C%FM<_Z@ICU)hc
zPjMs=7EgJ~sfOk85tfB|4WVlvUEiXDPPlOUONOmRFk7s~2x=m*>LoimY_my`5Z=>w
zrr%+l+1#ulX}9@&HmXtf2s1CZ#GATL!*5ljNt!^*P8b}r^#4Yy6JGxB_nc>4%D1hK
zGm~=kJ~j)kQB;JTC%?%@pn{-j3MGbW9L{_3_Pu+syhfj
z{FwTzVOa~Tt=R%%JRM=~;F)K?^;?D^m?}v+cpx$w=>Mf1A-wbcclaf)%|P>57sUp2
z_}=gt#r3zUy$DNJZv48@gGJhUR9I|O%lH-yi(jX$6W()TN52)%M#a5OvU-!u#li<^
zk<368(~5%d(l@+ihoMNDqaFo#auzG!tYS|1zfQT@8xaBfn%kc$7wYwSccEOX)>pUd
z0Fq)H?RO~cgkS#TZ)+=}9j88JQVc!V%_+ijUi#?Eta31?nrW{$@U_q>cKt74mvKrG
zzU-xkuCnSlJ7^>2t0#`a-SR|NmGHJt{=i2RgHV^R8fi9ci1SkzwHgGaIRB7;JyDt`
zJbd2S|7FcXZcaE*s`xAIZp0cuG70Z4Zhx(nL#IzrGo?odkudWQ4>{3_Y(5{|>_1fRoA
zywuxiC&KRqwJ72*c85z7-~A{iibePI8coIN=$W6)iCPT3PjHLmj*KMUy1Q>D!YgpW
zO#YL3k}(c%FII|jEkwuFe7S)8nUSHN>4c6CzcB6U`nRQO;=y$B;I8U)F|#EzQz+Nb
z!5A`Wa#vvijm}kh*Kq@>hAqk#7N!>5|Yr=s|}f8cr%@)D)3gIhr7Ux&c=MvF>yAlcM$1dL3ORmU~#
zjz_u9h*^!=VlikedXYmqS-C4?MFpksG!3!nfh2o#ovB<8T1$P84qDr(?;}c5df1P#
z%XM-DgJyI<4J&`PMFMZ2j&iOuHsT3p#oihA=wa$8Oyh%uIj2N2osbhK6UYEe{sN-f
zX>%^qS*}hM@cEC0>V`~m8fQA^ku;!BJ)TgXK&Bk>@Zp9`XQNP?g+P>Q_`5z;sEpKW
z8x@b2ZN9>N5T(Nyl5Ca?EzM+dDr>1@)0M#-PDM!8ks$$wh*PYVF74fY)B25hHzK)E
zo%HsOj1Err?-?K7Io!W>a_gR5BkY;X8WrRXnF2rknCr4XI^G
z3pR9gb;9_i}0$4`m{SmqU@@J*H}()#sV>Gg(pW*-URHOKHjitCwP8i+bOa&1ZX}mn$13kBg%cARBP2N^Dqo3}Orm
zNdwj`S-~>V=tM0;>;g<1GNM3Y1I3zWp8@ibiZKZum9aSIvXhfrhejtSvl}v52nx-U
z+~nl8p`C-1lU;Zi;AH*y(2rNgZEmla^Z?<8jF+-lKa}(iDAxrh`#{Q%4ZWB3h#gHR
zTQNiFMBC&-yqXM~x8&-areN#l&6@{@w{@t=m(OQRyb`;G%muT~4k=D&4lH?lucKok
zYBMfmZsazh04Fa<;_Em`5TshMu~@697V78#EtD>9GnOUL_9FkIq(K)gxR3W@IcJZY
zIz7NtOJ2pirq+aD7|ZmKkU*Oa&Vct~%Nnj%L=2rYWF5BZ4sK
zK@%MDj?7FI0y{N_g~_AZmj&;-H)i)|3iV89WF
zh%icFLBhY9Ywg<~Q4Lj0OcaY;pe#VLDfigK#Sg*BRi@LEQp1%DTy75*YLo6^E}}o0
zFuqGp%zL(ER%h_92~S^UHCUWd+VypHPYIchB$%286nQ#?Nj&mQMn^{%lSG997yj}xz$)qh~6IJFMpTb?qjU2?>
zNqwF|-`d`4A}wgySzIt$Fky>C9iK`v(vx6)&hHDM$fPtW(q$o>=I*7R<44qDoxB3y
ze`(n{YEWH%wjtx#)3@@a*$)#q5h*Ia+WXgj6m>=Ipu*7|m~$RQJ7819xd|t#T~hdJ
z`7f(Ybi%6P(6=PLCj%31Q&1sbw3|fA%wioZAWr`CihIJprSi64WV|cr{!8K7m{|oA
z6ZQteT}JMPs7A61E5d3;wl=pjA+jT}UuY646kT*(?TMs=BIkso&Tl0|vN(Z%cxNob
za5$;$>gvgI{s!z|yCi;N$ZM=p?XYOcc)goauMf;
z$roV6vKtmvBovv5C5|0%Lx{x#*m(e^?GRdJxu=X0x<`p19oov?;@DpUg-m#r5=yIZEdMCLlBnN|qaJ)K(jdNHl
zTnZg3HAJww(jqwvI~rOf_?y{c#OFmeAl%-!H(#O^*%7iYB5l9eB}M_)a@p+4;MGA(
z1C8qwrG&fyH?FAFVG;!QdEvHynv$g8tf|yxR2i-F%EPuwkFOQ(Mg<98h
zC<$S?DvLIHI(${CYBq~TDA(AZMz5fr1MyP(wlF7%d~K^#@I%_97rpb1Z`
zE%b@wDPtN9Krl6=AXX=}&xA)1ZmZAkIm7=xqLqwE(${f?6EAUT|=O(-huG>>lVgmNcWbm7TO=Ih=)c
z(U_XcWv{lNMZfo);4@u~+`h|tC+hpxclGujp<-0*_)F?JD!hNsJ=}Nte$@m?E<~Qd
zxYx#d7P8{}-pu^;**Tn^zOpo(%c5~HCsZgqq`$cSJm{~tG6G&DHt6hv7kJtBP+rKo
z6`-OSExZR3GpUcFy2*;6u-_CupuuL?JH_~jdPioE`C-tja7C8zB$glYI9YcL#8V*&O2xOzk53
zCdi8F1Wi;RkEF##9>%<)i4XE#CaP8>W2N(H+%+kj6jrFU6tdUXHbT?*>kGwP34$`a
zAAOd;#1)Jme<(hCBG}J}(f+Y@U|CN48m(9zFF;<;fX6GDPEoL!v(haT5YsVRm$Yv2I&~qj)KeQrm6tp@oFt+xUg7aeJ$#DnuqCAJmEZ!)Xj8@k#7^g+J1K_IQk?
zBkqZ39T~a!09Zt(UYIEkR2P=ufflDU_%T51#S!hdjXn1V=4;wv#;b;MC?*+I|3@T_ZwW$1(2w`z{se2*De`FA%g6?e?#*54>
zn>7-GFUq*WDitFbV$sIfbQ>922)$cX46_BOEsADL!BPeB;n6rodsaK=qgW!PI>Ogc
zKzX0D|8!3%RDf)I%BGEsa6M8XM3fxHnzwa*Ejj{xJr-FNv$FCzMtiP-y9XZq;x&3o
z{F-`W@jzGo{QQv;Bp2K82DT7Mf?$2Z^dw2xe90~!-VHdAXvn$|C{?@r4k0hW-94P^
z$Zp0581M!EZe~)4F6}&Fprpznfag#k_Z6vRoLIR3hTc!etqWI2s>9GYkA}l(`z31w(ubu5
zpRcXXWWp>ZmYec!G=vu%tK>)>_^kc#W-fKVq2xSg)Wy;0A8())j<(9UcN4NJ@8n|fy5karLlN2^{T-hS&CuWv9g(O^0^>CX9
z+sZrdHG(Iyt)%RTm(qw79OJe|B8)~o!i6B&&Ta;d(a%;DH+3|4=eeXcAn{8&*Pr}I
zUvBlt*l71zK5z=9gqz1;?OXh@y=>uc0&f-!_8KWB?gq|tH`!WyoX{^
zUS+aEeHQU1GG7#a!Yvqagq@9qu!&Yvzh5CVhNRMV*|Bi_zEN{
zd(^Gta!9HcC5tW`hEPSgU1^=n
zd=ouiuAYme##`RhA>>akR+^f|G88WhS|qH#k#oRllQ=8ra1Dt7<2ha%X>SIoWVXOt
zaFl4yN5u!i^)V&2#`CD@7eHfwlNnwa4Zp*^Wdj6f*0Qn0Dx{iyM!
zJM5eI>b=Y0`$q8DoXvE|aQIXet76yDamFPoZ0JxIx;;4yPdsa!bC8w+7>*aNi}F_&!9o#-VIru0)I*@YbUS@A{u01u&tUWlNfyTO8}6o!AQdpnTGg{aynC&
zwYg|Fz*Z<1D;{IBS=N`?D1#F;gm&s`Wdu(*WH!RoP<)cSs9k8nDI?X~RVguUH(gF~zbT)T_(Vtn)9RS%PXoImW$$*YE>D=z{lwB3czb
z7u(F~ei66WJM|=F;VqA*V1TqPy%a^qNf*A6s+OFy8rxen3n053@uPJ|0D(FrqFK_E
zT&V3!++Do<$Ch++bV-Fxy~Y6guzTCdyUiSoww}4Q@((j_1$XLr${QI-{xvj>uvlrP
z#FZ5Jdbai2hF2oO5gs4*!`Os@zc?0+KB0_@0d^dRB*cjcB|2rrl|;
z5Q~ONLjGV8>4PGS=gOP)U`i5&zeQI+4Y!R_%#$+UwGidhpwGi5lR`-umYjsl7Rx5F%i^t
z6UPuhrIIN7XGx73*Ax*B>PloYC83ekTjae4VwFOy-&53!<(b}IV+LolkwmBx94{2o
z;Y-&S%9Gx9Ktd3*oAu-}eQ
zrwdYfkoVmcD!AnCDY*~+ZNsU%oNorlK@%^vmiUPdtm3Dt%H*z{o^~bp0Jon
zjh>~6N|UXR71288ym||ZbwnJ(J;{a)L?oV_OS{3UA;uYhX*uPjZgOmJq<^%3e0UTS
zKSW9l9UWmuM3ISYMCFWbj*a(^4-HHX?H(HUjZIK#jY;6&O*Hg#WXH(l-oeqaq2b+=
zLt8U_Sc%;1j8X%*@m;0YROPZecI@3{HCz&0Zk0`B9AJ_2kJ+o>0-vO~6>DPRP8Jb6
zm@ZDE0+zQ`tzOIdAY%<@dlSXYQ%BO2-7*nQYd=d|Z**#VRi
z+XV7Y9M1w}C+i7+m*l8s4TQShhGXkj(vDy-R7h9p)>ITL97Sj{I;GwuvIxg)JS&n_
z5)ThH{q$*K>boArB@&vA2%^n3G0}72i_0!dEVhKAl7?MJyZa>nH3)jyIL9UIr>hDgd`08)A^
zO&gx1+>_FDhoi62G!jN?Tvy03GU@n4#hvbwhMjlOkhK?2Lkz2G$MEP7HP7C`h#{U%
zNX&qS@?!{dvPZ$pNJSKlgQH1~`a>{bJ54Q)*zq|vfVo>Z=PXq>xwM>AOHFkK@tF|V
zP;K^)4D9hDYm-`Jz2CQ%%LYx;chtc&1S!8P|Ph5}rR#&tHP1w>bh%w>4IRqh>>y6s2BNNV{
z5lvRVYPgQO|2DPJR`1i=v5aHG5*=3Zia&uAOMoLL?v8WMlKN*#O}D3SK4LW5K)GlD}ADK`_ADD`*)7*!)brR7Mb%hYT5n!l00-#
zG8;!5cvlL2vKPmS9Rka#YWzfxUEgI`+A$_5mcD~_R?{}{^yrd<*EU(8vXN~vOSfds
z-gwT&jeg9R+2lrsV9RZ;w`Q8>Fj|qt(7$Q22{GQ$Mnu;n%+#$eGXn3yh>zWC-sgJE_Sw@;wfcZKJGA
zs2{c`GpzN+E-6=lxcC7^C8*oom-M>Qm$TBlK%&@$^PsJU{SkKpfg8u)EbX8X@msqc
zzO2URhi??E>Y2H{*sE1*EX;m{t*cEH1X_^m%J`x448rXVr^3Vpo#*1O*S?vnkv-Ek
z`ja<^_#U=wL;b$Xw7>wM--VHOaZ;O6xO!q^%3@3tbf@f1CpMD(aH%21cW03YJI#0N
zoq)#qIO40G+$z&_84#p^uNB2zS4nyhsC&LJ*rn_3l
z3t?Q)(cj3yt=?Ei)pB^^!VEm|`AfvY^Z-v;<_&~u){~XZUzS;ypQm4rOL*tTPDxC2
zRWZVmAybD>wbGc$$xbcqhwNNKAzM?&y&TxP$q>p&8UY-16F!&hjzJOIH=H)&&Nn*(
z&}^*kJ{`o?iyobtQpnv55=-vplz?J@HAOGfQ2)-MOVyr>cfwm(@RWr<71=AgiRwv8
z-YZ~M;Xt{{i@17wabe_iZ~;S?WK~A)83a=eZoTIOVgogw|eD*u%VYcE-EqV=V{i)zpyo&gEez`xm6Wpo^v_gQv%y
z-(#%P9<*@H#ZTJ%C4fG-c!mr+j5IWen~aoc18^;PXy#Rx+zg(lXHZIxy74EOuO!~$
z*tzI{D=tMy9EDEji)`umr%RIeZII95jcF4To0;cbT$q^XqZZxA>ooHF@u%q7D;E*=
zW!&FuTqhwC{Ad0*6Kxd(>$mEU-ueQ*f`BwVe)VO@dh%5EWO?+O$7)Jaa|P>Kzw}pxjMgIC}ns)V3;ga;2M+S
zJ?ZQb$anvB!3U2ueDxzab$hIYdtRg*br^ud8rPPDsd3TEemU1
zI}}CoQ#Fj1!O3_rg#v040)SO#}O-7m&y6$4><-ZgX5+^-GvvMfS
zz!8Uy{)|~JGTiYei_A{m>23*fDjLlny@liC2xg4S=@p)1W*AYjOu4>_J`qYGIP|f|
zP%5>mY~pX^0wiy936Yax(8O7(p20fro{hB&qb7Ri(sX$Rf=HBGIY=oV1*<7dFSxCh
zIS_F<{-qK`vYq6>jRMCcPKDx-mYtDfP4^Th*-{=3!IN2&Q+jyo&2Q%^@~Tl0L}fQ*
z)<$hbKZ?`nt*4WHat50n;1%kM)5>a5speKR8ugtBd`e|>Eu}Ube{|~j-bZ8U{F$AH
zilx~(UbjLQVW8hVtszoBx=~jQAvd!O6U%TZ((Wyp(~-qxT#lF|(L&xZBy$?)nB)+%
zjgYk%jE}Vf0dTlMtntSl9KkbvyZl6P<{t$_fJ$L6~cl?WB-Kj$?tr
z3BXC}fSCBK?Ub3pmfSubmSn9^se`ri5D_d{`1Bd17CW$DP!I{XLPDeemB4)vC@^C%
z5SiTC&XSZph%!!PbmH~;^_?Xa!v7!S0I(%(G#%H~a2ViC=fQnv@)8`8CpWnd70S4v
zME(XgPZF>e*lm5qpS){&GG1KUG!g=KZ*PRwj#!
zo*`*o_33N(8+ON`^%7QvQNk)dvgE>e738Yp69^TgwkSw^1!1V&2OOF{prCY!
zmEoKZy9x{b7&>DL@~?%*MDCX7J!ecVq-7**PdW=DNs^+nqeLAMo3N~{X?(_nXV`R8
z$%F8;{vj=FD{*fOAEA~ytFm(cvZc1cLj^NAblkZ%#4W8TwWkCvdK(a|B?2`tni{)t^HqP*(L#6<^nW^so7pgS91frKFL}Ui&?o6@y;1Kf#h1
z=EQLaE5;dV-h{AjxLd}Zaq1tr`iu=n{KnrGceC2rUcsz_A55^w{uYM|qN@nqhS85$
zgebOcr)OhHh`^!XL>t^H%
zoV9Ty-460OiEB`_)PQ@2qt_<4m520kxrn4|6l%p>nEeXwpV@OJRYTtvs0IinZd3Op
zAgGePrdU~=rw<7I;Ws}x6<|J)z6+pSWZ_wugqp=YL)d@XVxxM?7bj}bWMr@pL`uJ<
zRoE}wCIV}A8%ksfjm+vb_0^H)mbBar8F;>?uE;FGD06Bb+|Eef5kbZGi%6(ny695J
z?AEI-mv0OAgK%B3D%MyDoyDCX^O##rO<*J#gcOgX^bBf0xFOQD>E^m_i2Q;Q>AO~a
zN1RcYIxJQalbNOhfmm$Flp2}&!V*_8M2iYIyv=p6TB|KC;9?6IuFhhuhKE}+0_Cbs
z=%|K8LsV19%Y9iT!R{+IiXZ?K!olI4RPy7z97&dTZq%)ek}+2-FU%~K#Q_u5aYO|8
zDM;-Wc(c7a6+LvGW1>o+$WWlqvsX_XfYVfTDohLgP#6!6dok|Gc1_gfqN_+6IV4R3
zLJ9Xpz_`TXgCO8UeO+!Mdwy4L;?Vl8iHXzcuB}Z}uHpY`UHH|r4liR5CnnCwoxde2
zbU}`pAPt4+S99~L_g%J{p5guL*5|}S9R2QEw|@0C6YxS$z{eUZ;UA#L
zM7B-CG?#=Zr3Q=gj8wox{>zHWPRm{N(>L{$YvTDeftXCSM_&aIyrIp+?no)!!t9z`}NVr
z?xzP&C%PeRE?zBdyZ>C%OhcvK=dG76*G^qpV!KVPr8Y*n#>}ZhQ27sV#q}WEUFV~V
z>(*!XOi+i)=uLQ9^O5(XAmAK~nmHo=XX*(`qfX1`;J!Pt@eU
za_=3Zb=IAI*@`e-N*8t0@5f;gWv0Ll{1zk=wI7H9aC
zfrguj!i^(5zg1G_%cjGn?Elv8wWR43-nln%0~*XIYnzvrvWT%m6+{
zRD?SrVXk)K6Q^^<0WA9NN;y3RzR8!9?>1Vc&RVtF2;LPd^s;hW?A+PDoS2Ke*d!=S
z^T|HRX|{9XW02uDDicl;NQIEPRqmVj_|bG0x(-^6flgYM&G!JH4A)|@zg-M2`XZ$U
z<=8J2Q+bIRf>Q%h;5NW?21*1kY`oL|@UNqO0;6mfl_Xq1-t1NojS;O9SRrzkU8CDz
zm?52{{F>c+c3zW5UQ1s3k<-`LbTV=r@x`r8j>c$YWPO7mx+RyQD@}I5=b)b!a5cyR
z_u(3oGPr(Z&6>XgjyN_H6~roc?z!jMF_jKYZ{by;?wa|iWLJl*nC?Mh=NDZBgs*VX
zGI!219D0LHX3!{PvI^5zVtx@y4#x)C!%+#f?2Uk)%t);|RV+@^j^!Vr<)bCe{tVB#2ryUBXkF^qz@DYe{e!hj^JaEIbN4I;FZD9BR6
zT-_%*9dU{5$?g|JI7{^^vZr=WoKCTOfYk5PV$5~*+6>z$$RlT&QzGrl^w9)l?Ed+Q
z6vu8*LCp~Ro@{UL*1-$+Z0DM+Ol1EOpBiY!E4tX%a39l6VVzu8>v+ehp_^HWX;eF766BmH4ti(+Gt9hD4$jcS+YE_ltvvr;7O0
zXtlD!E4sE7FgVk6s0wG{gEhMG!OQm7ceNdRjz&!FT_#cx_H80J(bbz7u9TNDxqZ;^
z_-I51H@5n}4=lkYA!NaP5lxu5Obm>e7p2&dvHSc}Ce&s{`#wOGfnw^e`rzVoeCEbw8YveuBBpe5Xb7O%VNi
zEage{#b6k}`{k#hi>dwLCZTqV66Ds}Rv*3fJc)5i6(0B_=DP@@eIPTTK35{t~
zj#(JCY|<*R(rQ#d|F#(Zam1R)M@+#W*2*XUR~9m-L>tvbe3>R~W=WuE9$#R_bV(5n
zXfnQ%AYglS%Kb;uev)S6_7hAPTb;qfV8BH}L{aCm_w_RChPb(V(HM#S(lo=9(|dMm
zsJ^>6MU`2@H>@LLD7VVROOl%k#uerplSrYM&S6`9_C}4SCK0)4-;Tb0Mr`3f1Qdr~f~s
zB}k6kAQQ?V0-Dqa<80+>U<%R&pSyMKj3x
z+4>K_*PtY1)Ulem5GP&r8J4w_TxDp@8GJ?s!CPu%VL*r6=&_p?e6~QO*)I2JYq366
zE6E-%mmR~WL+bJk1AOiSxsFU7p$6p!PilGRBcf!k{tg84a)@;e4v%+iX8AsZdHu}+
zI39}p8L7G%pNGR+T-rL`(bXZgK?>=`!#FPmT`uhOEP)~-dpBeftjctvCwSqz>2%;Q
zwUpRCamvhdiZs>{m$;pA-_W32T5^LBiZnVmPhsToywBHyTd08OF+VH?zv&Vps`C8}
z_#}XgyYq4tF0QCV3HwQG&LsMbu3irR6ENUQ3FXp(TA_y5Jso4%xXm>h3w2xy3B8Io
z5gjVldaAYAyihcc1k!LE@NpR<%(3!VMcKMFv8^C}v7^c9g8)Oo5;Y8A;_+w^Ju1=j
zrk8?1tw+yV7mmuL6F4SfJK?|}4z@Px31lPX#o1EDX0Q}VFX0NN;z)I)H@nJhMh9BJ
zzA@sOTu~J-??+K*Sf=iB72Lc@*EC+&)TZWeq6Nc9g?Ha+8;92PMNHoez%@y)3pTU`
zSBw*e3fpTC`+ka7I9qXaod@8n6}<(pgKltPysy`}~bD5?zy#1P1z{h$^oByq#Y)Z_=-{Ng%@P%30Y_1$rM?R7qdo}
zdOBL$20HKeb$7cyY=JE9&)_F&Z^_S8tN3Ar?9&;vf4{BLe>ii(8By2->GES{rCa;t
zs1gQ9R_oX%&DiRaL$GO)jh+=1&mVqpU}eEUUFt8?@{e-ideqxHFuZG5|8CA^l@Exc
z*+XXkU>9aBhzTU9x0c|@<>61aMb=-qBPM7|N{d;@NO|~%`?Qtfbc=8xGbD6VJ(jCdP=-Cx&a{wU9~^MC!t#bwZ>PBwKu!
zRnV>}$XCmD_^wqWuAUgM$Vb9=5-WO(jP5j)n4CG{VAc)6B88*fi4^|Oj^R!7lEs2)
z?`?^RzfUkG{iRSGL+h(i`=(b)s6ZAnaS-P}#oFDe5^{lV1uJHvb!E|XFY4oLGfD#@+%`UAI=Fj?ivL!jet
zl{owv6E$L=Vftb`Lj+G{UvDkW)SGYi%5fKZ2);nzRMxP#CTGwQo9wn>`F5;
zAQyXW?R(%sV@pA#+_U(&1^~_sd^dZFCoY&Na}-RECR9`Ji_ErxE7Fd5h|{28U2;uP
zM@6ks-egQ&RSBZ$Kt%C&KQc~QDbOjWhATkjvP*1XG^ZM*IF?sx?-PDDXKpn=6wf&>
zmn)Z$RRoVtqD0#SSG?Q}Gi_*iIp3FffbJvDeES#xdOweSUBDcA@;f?kCnSBuNE+m^
zztDg0y=+<)?t1+5iLXf0ndC3(NSWXlkq{2wI*f2dBeOh$^M8ei6AgCrKQMb`@0oVS#k_p
z6hy+_AVNZE!BXU{o1jxPn-eIkC6VKV%$DjSwd&y|v_}qx0li33rSx3Q2TU0XT~c^U
z)jzPWU;XaFu}8|%I&&J!Z6u=Dv@(&?8V^&tia`eAgh?BWZIpqo^_v5x
z(e0PV5L+XI8BmXTZ4lEPDb%nuz%WpMf-^3&-QAUHH?Bb`(Z3>z28`Zy_o0S49#8-#
z{4At|85vw}o1Hyj8NdHP2XVF$%Xx2a&?Q$Jx%lL9IAF&vLEzMcB$vP(F{=rA5Q=wWkK7sa$^~47(`H?)DP2okLrGqvG#&FISFWH%
z#WhYlYWFeY5CyCcs;(u_>
zBF_MUe~vpisY%SvU2OWM}7OSuCxNo37n~P
zODp?cgD=k|E&)SZ*J@5uLK5qjW~aoP)>%aVX%J5n4n&L3E*I!g0-OB3Zq_xgYucbV
z?vp%F0mDrKRfJxpH(buedCX^sHcU@P6mYw3od+wxR-V4aA#WUlbf++oiwHK-tpVxA
zm!#FLx`3009Z2YQ{?=YhjKHjEr6W}7k|k%vqLi&FL`ub-L*l5byOo3OI&=t$AJ|tO
zqzD-0=nKtPJyCCU#7GRB1w$1{hql)J8MmCB<_c~V6F;0Ha#zqGSt88%BMThcY=+}Z
z2Z>k}@Hpl&Heyk+5ofdPlR^)48^MeQb45OQ<%~NOPbP-V91VGq;3Q>=Qz(6o;4VXW
zQe4|EvLO3lV;pC&o0i5&^)s1TBVS%gNTPGV4L_xM4P?{}(Kp}-);j#@jjs5-SM+Xgh#6KZ
zfa2g22@yKRB>kj^-DCDi9UmYO?>=4bYnUGvEwQ+7iw;1!bBiw$>_=czzS!gaX7m`v
zZZPu=y7=Qb@^|27q+8KB3vs!^w0HcwbYzl&AQ`W~T2r?WGgPV9*V4-C0Fxn64x4C7*@gjVs0p?XLXKdU7Bc}j`i`=#{m)57lV8O#%Zg$uGe
zq({C4@HxklDJX3K)7h)&KnjA<&La4$N)4e)Y%|2`h>M;uv%2*HeTk^1RY!lwu#~HS
zgVJ8)v>D})l!!@KaJehQH@BwV#TnhC{ljNYs0X|PcA;{aa1r~H18qx@jD9#!&W~d<
z&0&)w=%rkZfd?W#R-au*b$KTfK#zc}Qj5pthS#&y@^Yw-i?cWeV$)t$fnrUzm7*1L
z*ze9mt}rU(w0{rhn|lvchB3ov;v(Hb`NC=q4GhfTh+?*Vje%mhJX$>jzVW(1=L5-Q
z!Sqa`9O64@-wZk=zbdE*>m$uW-6>M)DN!OY;>>kU(2$
znpKdM6Lq?^Vmd0##O)_MAi%Cmjbr0JT%<+P))+A%kGr@qp(6(eQmf{)Za1?mS|cdc
zcjE3tG@RoN>+B$CkgikgRcnl6QDqTm&Er#QE6{1G%C$*%u~1|utf}{Lo#l-ZeY(gB
zt2m}^U(`nV5vSzfP_1bL9yKQ5X2X6)gxzbvgtm-0kEVU0@d7@Ch4Y)?vlKN6&Q6tT
zQ;X$7?czd>SEcy{OI830267R|S+VG-)e_H`iHC#MQkOHnfY6DCq^Q$6*jcLJvz2oF
zrgaep6aPx<3gryODy2SzEl{~nPWI8J+`)s+V
z?Hc*7`_uJ-1W{*&d4rBjr^$}~pr(rvz}r2*dzzeA**}YU_gCbc_7>jp_^0*iG@jTO
zsz48&Yb`v}DTXHuuB68|Jl83A0d&j7RCd~`zg&VdysoaHtd!6KY3Dx&NLzIFY9cE)
z)G+U>>~|be-Jiw^kAGsPq6b+5O6)MpG?LKds26ho1%=+w3m$#U))B0X-PqOh
zfAx?@>Lh`xB8^NOzrb(s6xzMZH<
zRI43bdQ2d0zy$rI`GUMj`NoQWET)YA1?2!YjDDEQKLtjoRiY={>m-ugR%;%0eDD*u
z-#{CJ1YG144S-76YSImEM_UqU4HSv*mRK!`-G(ks7w@t6cd|*h+7YOEK6csL%E@Zk
zZ_Yh#^<5xUMJ9fx()^i8r;)6)BGzF}IkG)}ykg8&M2{*jx|qoLdmY^ib>e}
z$9#HTVmvOd&s??3Y3HDd^JFbo!P(`_=VoI;S#$zwInjOhaN-$AUUq-Fyo^Mt*h=Kr
zCsvZ0I60DByT*$1us_oRzrOm228kKz)W7M&$3Ib64+=*MkSZ~Ur0KCElIWXW
zS)UUxkzRr?Z`~ib=|_?@vKm=9q%4z@+vXtda>O*#-R)ZHN+nso{e@=e
zNp9EJ9)2TC!maMyE`IS;ayK-4Dqgy?*adRDH6wPSl+wNkqm_47C}Ly^r7&1H53I|d
zq8^e<95ROr?!flcTor~&EVUd%EXGTZ99F=d)ybCpOl{mg6Tbp(jmjBAgpBpe^035c
zb;t?h864J;vnlInKG?{~x3J{{H+GWUzW*4b#k6KF8xDXCALodi6;F;zb2Vwt#>QUv
z_708V{O4%@v%q7+
zH4UDt)??Bs%>mVTmC^{Ii4)JZzlNV|kkJLq2e*F^C5$b6
z+Ao`4M_%U2s2`XP2yzjv|
z@eJ&4f0xRELahg^Of=)}Jd3rI$q^6ZD}{`^^afX9!P12{E20N2bghJ>?ru?mQjsJj
znnxm^L;{>s2I^`hI7wcQui-8t2=P7(GlZYED)4|#jARqY7Q=;mMWIUYl@i#TPIUE<
zel$jMR#6v7C&OL_WCEd@oOVIW+#>R2wYHK@**X$-1Y)3?RpRFknJ55ukCTV8w|a2v
z(D?A^>P)e6ke^p8k)w__xG*q?Tr7Cug_~!#5_pv;NJ#w3$RxIq$cWpA3QPFV3(s@N
zAtc;0Q9%q2N=3|xo=iU^N=^dt-g|t3CBB9jA3ItM4tlh#UN9zNRc@M8!7WQM!_3=G
zLGrXLsouk(!P>TWEg{IfdOWu{Nefexu$)$*649&=r^*NcF(e#NoB%6yU
z(vf0(komwMEWURGB>**skKD*^hnp`t785=%?_Z`(iuXQc!W|765JQr?NA5B0yGClh
z5?wR#*2xZh1v-XZJh8d#ToqqXa96=QK~+?8$;2qZwYkfEj`F2eDyAq9K
ze(B)8&%g^`9Zzk8wBa+g84q5=99jlujKySj&EwXnds5nBhK;BMO0pTIPn?2O=dgz>
zDip2_H~(n)IH?^6F2nAO$-(ACnTVp|RAa{0sQ(Dp?CtXzT3wyuU7(DSMNJu(^q>aE
zm=@{)qOMx+)`O*j2Fl0nZ!}+I4pbrW$>RJY=-2n_raha-eneW|usZocwf(A@u
zE9|}KVZ$P!_p}#(nF|p~)M3(ebPBRmPMhUyNPdYj+NritAu`N5QN*cmD*rO4`QRH1
zHY1b{!T(wIZTwpf&rpI9!TmqMLF?s#S`{()_oVz8Xi3QFVj8Pl(5p#uSE+NZg0R^JGs$5HgsrSQR;P;KMWv&(@8
zDXN^&%9&mHu=Q4+s#^pWnuX!Bvwk~UsDj6F~A=T6}GKQ3+0pDv~3Zs;rE6Zsmcr|gZWeAu|7AEPS
z>u`rOoN+L+IO9@93;(vGhaMl3&%K0?4F5L23I2Hj@#Es^qRC3}-ClQm&4;vj!`Voy
z$MXR+PjBHtB=rvweBOcDkdMWgQQd-CZ;?uz?wc75A%o(_0H*+irqIMtOQ|9TK06YB
zPSl~>pq^?rP-Bw^kZZ`lhzAe*?xZ=LLfJk;!YhTFQAjuxp~{;q7^7N5dbj{(7`f9ho=gu&i*~l3
z_#xYef0@{1iOqS*;2$7nu2ZfRFP8t;(ObG?z+WgG^MK!LCH5x>^kdn4SPjKIP1k
zeJ-#=2})x_&9;dU07}l1kA)X(W;Y#f`AIV%4w5ko>cu^=RfG<*2QEE9+F*dlwK>4!W;38*Wt
zhQHL%{V}41I<5Xo77DP!1cAwwVTfXOXNL0g949#c5O+zwdsMsE=CeZz_av=7v^=7qG?qq{Kz=s*Hq(`tL=C!OSgB4ssS??Fv9IhP{cN>kG
zEXo3oLRcbWl2FqQ<*YkH2s4-}T+GoaO(=C3#1M;r(z|MRnl#5GANdL}jzS9fTv!zQ
z(CZEc`4H7lkxECNt6Xw9Y{B6Qhdq+?1Dq^$&5Y0tpuJPwrGW}Y8O&;gORv);$BYV|
zx1LX7uL0zX%c&D0_H#ejRBlO5EkP`QCm3(YHXR3aVc?=rUZgqbmw?+hXzHFJ%W!)z
z46Zs6P1JQBfUn#*gEC=k2Au@Hbzs_!y0GMc?>iIk2KO26iT9ZhucG&zjd;F%m`*4R
zM(?Ow?vu`k?9GRnRO%S^UQq%?aZl1vEbJlquN(pIaB>ou%l3BbS%YY*!Q#&c8nEL8A1e13PbHq|4yE=tO
zbbErXy>Vea}#?H_+OB9OiAt=xNYMKm4KcUjx&>F+S@LGq`b3izR5&q?nYdr
z5c?@tALPEVq&mD!Pp!+7tM{A?&JwfJzh^IIZf=fyw*oAxZ^5;yNrmohz;Mzc-ATTz
zKAmhBY)o?F3r7wg^H!VYUF9#QQ-?{orc3~Iq*-pQdQx7`_@mCpH1m*UVVrB8!=Xg<
zDUtD-8Gp~mEvW{^v|3|k=eT(v;J_CoduRotY?I{x1H_l4NR@67CZzTF{2X%plYrT(poRCE0=x
z1)ukr7_B@1b`t~2Rk;b6;!#98lg>3#HO0=Xpk70$xiu}|akgcO9U0Z3nnI~Oycn<+
z4s{uOs+vf36ckov;xo=HK}H)6i~~|>?7{h_*Lg{iH0>Td(Ui3+*91Wp_zvf(%tTd{
zpqd2H2~7h=JSmyUJ!2-}vbjy}Z~D}tNWKG&aw?1GsNd{t2KIvoP_)pI^4<9RRD1m%
z-2|=od-UPj^7rVXsAZ3xp)z{tT$x1-SRjGEvs4?VxKwS-1<7ZExC4M$G^>tCeUfO
zP!@q1ld}{NrGCFs{~|R2nK>ZQYFFm%ky2k!zDFH1l-!qU%z}G9
z-1N-wj|NkmCYNe2;QIZOdrDb3G4dq%G<93
zV(GP4(qzW_j$gu=nWejfI==j>D;uNY8ku&-6}!saAU><|d#(6+$zt`koqVAcJDNBT
zGjnIv9%2g1*bdcjINfYmjROg3p1`W?LQAFD${LAvPBu>%mCr>uvhaQI!(bgs;B=x4
zk7$we`J0k%o%K#GT=Mtm6BajI!&gJqPx-@pc&LstGM9T5bJ6Bd&ExP>mPeTZvtY1a
ztc5%cnR5BQK=`e#EQ>8QU|bB~1qFQC)>-+~D&>4&E-jiE)38b`NKJtP;9z_W3vfrg
zl@-ay#Gl!fxQJ1@qZ0kKi6*@|-3KnN7@|NYW%^E&JCb0DRt-C}RNMHuMd##|
zTCrrSB0@CnPVlie%aytvWRHWGU4O3=;AEmeyr#jr?89*M74aYwmaWfI2mW45%&_@4
zUT5Xkvn{p(qjl1G+V;IK!Txe86DpeJ|6pzgY7WEi{tzqx!)+k
z{jWoDUqS|Z1-*G?b4i&uhS#p16l#+YifZv^&s+xcB2aq(M+k~O;_At8Nb~#|U0q&k
zIvvD_ku9R+!mv!c>o1Ghs7v%&LWWe!3&-7F?-*7d=&S6B@{727P(b2*&}Nn?x`PU3
z3XT&&xiFNRb)gq!1#fly+^bSfny^MZZZex$I3g@P^%kjpaTgvef$$4+D)CI{kgH%6
zGQ4c;*C4~sl8PkzS0ME6qdr#`|8#~Z_9321UpnFXQUDhRLZTx0Pb~RpcmyF?b0I-F
z77woT;>P$Asz=1dBRVfwG(`!~iF<{=WOEnd{`ieTeo5g`i6p5Q_R(=<<{c5SVYcl+
zx~TWvjG`OY2r*F#xR0?d)-@9`@INJcQ}o65qpLaj;>m>v9QZEg>BBP(8BU@H)hU}8
z0>r1iSMtZfqI&i%PXg*hl*iyj(+@Zo>0Hb|KoDxd6nXvLW#8~?7Hcx2i{cF*@rkJl
z#S;;ix`^fG&TFP24$|#kA-x8j=dh%ga3u?Vo5>On)D4qPxP_@{%T_LuC
z-e|OdnR7-1le1$2WP`0jyfu|U%*>D<%GyMBeXX`Eta<4Hv{rCcMq}m{Zq$GIQP+65
z^c#5<{FjqOAOf9b19w~8S%oHA>$>qr+%S!_qR4BG;2&CwE1A#AkvuaHh^w
z0R+inBSY-W_w?t?ABIJ`$l@F94W@=a08C6Ij8Q+x*B-P+OZ8W3N3>~D?2iynW{JRI
z7@T>>+3V;iryM-ysQy?;C@Z+3wPy5haD6^4E+`THyPul
zX{%J(KWz}bVasz+6{q$DQG7T>`cDoVR6|zfm0FWCJS|_905on*u75|j9CB*vdJu5`
zkENl4d(XII#~mk7(!M}r2*;;PjZD@mNja8
zqnVthCFD2g8sUU!mgG3{uN&|Kx7keWc->nutq=lzs4b>f)`&S#SaX({6a-LJoqG$QQGUP>}A)#Gs
zT=lUYrg^ccl83j_!JRhh4yW+*(cS3mZuR8KN~d7eg`NzCkPdxQRQ!`V)V8}dbsC^(GNWCR|ZWaYC27}k3>bgO2+KigC
zJ<-ASYHvVZnK$8i{}dNhvmZoM3craxN9=W95x^`jn$}a6416PXm34}
zBs4B|GVZ805I8u3tR;YWyjFpzsFgX}q}DYD(QLCSUp>Y;fn8~E;gJ+TmKng22RF-m
zNmQmzMsj
zTsX+T7A>8CVz&M31JSLiWQ8=QZ~OG7<^>`>R1T5)-OezI<&+}DPQJ8bWZ8&nV57kA
zB3lx)%UTn_?hTS0ewtaloQBp|>lk6)wPAPknw}5Z8g%ot*Fw@L64PTW2_=?V<;w|`
zIHxb9WalJjFcn+O>~%Wmfw3tc1}I|dnE?-zfkv;pmsw&B6d^dATx)q1gxk^nSxO7L
zXV8Hm2+S&OfrR{X7ChaQ&uade(*(2R&>s8WK-_WDXEM@rU!FEEq+QOiGA84>>elXW
z?pX;RBdiN5>xv&fK!Wq5?E?R>RNFkYvxM6eb!x#F(6)>Uz*I~E3y=&fGCy-{fd^0=
zN0%4jDVRvlc1z9Dfd=ySA@t#Q&iqo&YathwD{Y~rL<=Aux)pwrOc3hHKvr*Ert2E<
zK7kVpD*Is$j9i?vI}KqY{qb-hRdj3`c2Dcw*Xhv*2H#7%}-onE6;!(cPC@S
zmYq=?x0wB&0YqRg2gB>yflcI9$gStAgUd6r1i~L0focK>=TroiDP9T#cqQlY0auC?
z3}9vuX%8uKFa*GEHP;m%FF)rn)BR9`s0I%uDZvpi6jm(FxmdP>Yd9k?e
zM0>s-j&6*saVvXuJ$V7-!2wdGPAOwmP*`=pyx4mOu0pN9w3^GXw(;
z`q2_@5P4BBT#~Ji+l>`-4$c;H>6kZ?q4)8zd2ob*Bd78}{N7iVmoKj4!==ISte%@r
zk`fLpXN&-ox9@(G6k}Vlv1*+H0zod1Cg{3>Tm;fpMn%@ij);J
zGXztxNV8(OZW|t7b}3m0MV=bs<(Xu7ao!ndVu=KT-fm+!gk`3F|1BytV2dB}JR-v#
z@YFXP_W+JBD#T&Rkhq6f9m4DKj_k
zIi5qND}XM$h=s9<2rSt(dOrz-D;qd#T;3q?U*)ekfHSm}Eo=)bpjgWPpsxy`B`hkm
zBZ@JXs2tWkiKHlpGl@bLkR05x)DND7%f#`-0%df-Yu`MFvINv2pbH3<%BgxcPW!b7?Cdzhe?n?7lxx1t-HK
zd`NEVOYp?;Nq>MNCUQmQ;ihVbl4rVKJI1c=f?G{q>h3&6#2+5Yk!JZiFy3o#SvwfV
zPtrV6mB+1UKBPpWWIiNtGWnhM8g;0wRM(+La-x;Gc~7`0l+vgHH&dK$2sSZ18KgVy
zC&(M1K*`dcUGRst3G5L&rc>nB>E7fqB)&1!z=MDjd|7p4;azd#@i1;Ueq?tV55f-P
zZ`)aU>N|=b#a+Y0xLZ6&(L&@J!3Yq+8?|&gKMvsMu4@HODLSHm08JTrq0;Ln&6?fC
zYLTg?+NX_tW)UmV12VJ`*SO_TgG}x~Z|VRAF^I~HxxYZP%*Y#*>2cT)$BBv_D1Om=
z2!P5jJtqzQN7{{6R(7`c_K$YAw?l3Bqs`}g`>R`9gsMU+Iiv(+D4$rh%i@ZnxILLK
zNdV@&kcTCWTG}dpPNrssc5UEZZ-YQ0yWS{MC`&^~lgveY$`VrX{^A^cBc?ZV_
ztz=iF*7u}vm4qbV5HNwn+K6a|JfMhS_N|vq!NDG15iwH$4zK!rPs(#w$PLdwnk80j
zdqRYv2f=x3WW>in76_7d^NAmA8u!CcPPCY6x4}CTG_^aDy9`AZVNPf)&Pn}TJa3I&
z3l=d`2b3b0>XaKx@NB$OTN=ITFD)(BAVX6aedIDi
zG+h612T9*4UgMlSdp=YVqO68T#1%DGgL7rGN3({7(x
zDK$?pehC_2X6mNT_wr3;#mOqCaeH+svp_EYcKcYXB)XpsClU`F{ues
z3Q#d2zb^(5w;)c*kK=bzQ-jcu-{-90QC~V8^-f+xp}`+VPqz2=Hn)zzyg{m7fbxkK
zgkX~{zWK0^2k;!QH$pVV5bbeUg3TZ@!89tO_|bUa0nqv_u*x!Zh2KznSm|o{m}BA(
zjz|3a1Z30WQP*9GfJ|{{!6uVDmXbQ#g3O2kc$r+{|v?#Bm!(ek#&Dr
zyEDhRcy{7FrL^%AdnK^9%bF)Ob0hfOj#b&su2j!ZltxdbzD5jr0r4TxOzWA(#MpF`
z3fqy``56M~K9MLQ_{|53;hU85^!jSD89z#55@c5Ad3f-#b4-X*@$Mo<5eS)TxG!HAd;mpfsi!LV3lCOzjQ)b6zcAc50mL@-ih?v(F*&Ua%
z)>FkaKM#3so99%_s@~xLSb40-`e5xs^ME0do5w?!z4QWzuY?flfv?WzYZs+{74x?!
zXK9Y0?3hp&qLRobGQdd8-Mg^za&JH`70om5J`KKB$DtrcnCepA1O`>Cxx}j=pk%r)
z>4qxOJt;+==>#wuhA~r(6YypT?`4~-Vb6{{6Xm1XTh+FL+e4c-AIiVP#_$R5Zg+o9
zL_>mXfj2%z05uMgiD|!Zdp^)yxE`nYNgkcS;#rh#;lnqofk3WTfxnF%&jA+x&%i><
zP~i*VJ+iwQL{)aiS77f$e%oJuJoxd>;po*L9{m-(R2T7oyG%nhOvmhd|Qa%XS;;e6f
z3S7rF$r|DUa$o8?9>ZU1&6D9bggOx%`jz#{2VvjvBgF?3E$jo?GvUdNOULx)*TnIQ
z;XwpkFkmSq6pQ3MQ{LwWg**4W%{V`wd6+5Z!z7aVu)+(`DVeTF$?vgp<0!&tLX~DY
zzzZ2LFc~t<+P)&=d&Ib?)*;B=!`jZ%ozMh_JdY)eal{hAQr~6<{%w}Xw_Oyp8)KMB
ziao!~Lgu`)?$kafK85DkS)4zS!m)enoA7`_IjtX#_BM7_cUSQzp6W$#Ik@EJqGO%A
ziezpu^n7jg%ai`NcQgVaU~XK_rrm*q=djZl!|QAG;GWb^Nn17cHox9{zMtkwT@;(J
zNVRGumsM5h3o8X;`}8k!(0q&=_r8$AEjoG8tVp$(4KwzM@?=vrfD~QD`QFZhoO^Nx
zkh-F0e){qWsAKk|o8m6#xUYDf)jY}C3KF)=$JPJmtv4_`cf^}iR9d;Jy>?uTNC;N;
zPdac}NIks`Gt{L+_#XLF7ANhtPfZC%)VeFIAov@Q*>I?(ZFZd?yGZi?rU}ALjwWPe
zOQEB}D1~I!X)u1&G%irMSU^ko0!BssVwVK~;BC`scm|lz21r|&$Ct*;F0?Oz)~5AW
z{b*i6ohtt2JY0MLE$K9UH*Wq+D1#AwD9AI96ST*nZC`MH-NK3tRnu?>xstkA2?DI`
zH8W=9ht>Y5fCD*1K@D;U-n`V$URp#I2`&-37eL1|M159lXwpH5QwS&{+%hMHn*)9<
zHV{zCtEWvf3(0cKD1;kxL^H;YVtO2e?24@?C|UvDx^grKn`POH6nShO0zO4HLJyrl
zvs$DJYwGLl&Of{agg!(vigRwdQ<8R5PK4LeHE8PY%Z=TG`r7t0m?1WH)eeD2dz;(O
z>!<^fonj8Z|1t+VffVs6{r&+inrS0&8YapYpe6SaP$e(gm!F?0(!Ip^okC?z2Pd=f
z=r4h?&%s)taJj&D$|1#lSvkLSJA)Al#!4G0!O;AHxm~PNG0+0arCXOSSHe*Y>0qFP
z21jU&&_)Yxo8YYJ5@9SoscRpSSWAwyYj!3xp23XXwd=;9U9p5O&%YYfhbK^JI8
zdTuUpmg1)1KP9Mmd!H#oQ{R&IU_+dgVN-DD1uRdznUt;IVSUdKJv9{OB6du5lY0u(
zhI>x#p&wQR7wr#Q{So8mg54n3R@oRvi4l9n6kFax8>+1~*Fr*#bS5ubR!X6)$!5Px
zv?)Xs%J(}Ot*_86gvUvfUEFKqPP9mujR9>`r*(nJjNy}84l{^wr!OvH^XMFY3k^!H
zm*lMpXZaSJ+lSA=Q*LhSm%lr`>9$~g8+FPEPC{nlAy^omVhQQ7Am72%2)U=^#n_Z~!_feeOtvcyf?|7@AWz3ITvLL;`eA&&7}50;eHu8JT!Zzy009Ym
z0^z$ye&VDB&pUAJ<`t+u)SpX~hfOw5g(C0+g}*%o3N|!yA;FmeTP@z&!TJjRDARz&
zWw47PtphyLSmX?Yl^kW`0MJ=D{-}(11t7Us1~{5$tIuKXL42Ab9*rbf+J|w%Bt;I9
zk9jQ=T|Zd-H4c`DZ|F6BBYat4`5N^#?kx8XjbtrSmlcY*nBL8Rqx2YIy-14d4he!6
zSfzZqhhG;hv!Im%h(wjZ^s!``O5q69s36w^fu=@k-034}i;G)Zy@wef^Z>*;=Ij
zi(&6EO^%o+Dip&D3~aw%+3*$+mty7pl~4&R6>~rfinGlV){HWro2%dYF)__$mSDTo
z5ZUx(&s?+TCGk+V{s#NDV|qc^u|y6?27ttRyS~4M`hjv#dAEct5U$^czM}gE#qUT<
z0-SHO2)gR&R`L8OMob=ev;2zgSy7LKQnP`FKFb9IBC^p8*KtEr*0j2DOKi%KoAx-}I`o<96
z;a9SurFa;7u9=g7P;uI9pru7ponG&1*uCAT)HA&pc6YU}9Xhp4p2(iA@q^A}Bmk(I
zihBx39zdmUq##1w8lDng>l2H5Fgi%+7vk?C8S^F4izGhFs>qTf387b$*h4dGpG(<;
z8$lPx6ZMd{nLrS_7a1BTtOEndz-k9uWf>-cEZ}n_(z~d^$EQ>4q5jA?GXe7jlDFyg
zssr!hqb+{p&|z0%hfs=`xoXU@o``?%E(*vZ9m%-EN{}oufSZf;A;B~y^9*4O->vRG
z-+cac-jUxG9*vk^Dr7Cu3D)4u2Bmjg9O7{3Q3e-_)tyafKIZUW21!_;7J>>zVR_%q
z_k&}2X*~S&>i%-(fqqB14Q$V}wsv|(wO;3RjLlx6lKp67dy8Z5bvjzL0T&c>ks9_x
zmoD3v>};Z|12Rjbd_WItrlbnmPr(%WecZB)9GxlDi;$
zl(N5+-~>mkNF+6JxYYs9AjLcTjPpiu0$Zwy|5WU`Qv)q_p-5bZ7vPaZizJ7}-|#}2u%EaA;ByQxDF%RHl5~d;UDD)*?&lpvfDmkYCC(sW
zBpw*Be(oicnTPo>wt8=&*nyxpUx>t^@h`K|1t%xBxK{82|qo
z@pZVM1{UH>HydzX7TcE0H_)fhH-RD42qq}TC7%osd3-YNT_gMrr%QaGHKUjvj499)
zew>PsaUo7gfC~CV+wneX8=2?VUvM>uP#Mq>yCtMvdQqoOxuNPp+?6yLb7kF0g)fDz
zunS}eG9&4%ZJ+}h9%8WLnq7GWq;?L~aGTJyE^2#wTg&@f!dTK+01keswolNDJ_E7U
zuvL!d`Go6SG_*`ps`~1_pH@#ZuXnFXqwNuZB)OhwZ^Dg^?H=X8WJ_D
zm;Z_62cjdzk(8!uH|mJi?vF+=Jrg{iBPdo(>;k`JH}s4@QM6XP;W^LC-6n+trq>&I%ElBz`=yFqLFvWh91VswdqY(0<{BD9uV6ju}`1}v5db`vsli<
zTS--bL>BM#@r%A=MQ^~kTKZU!v#71D)=p4@qI=qv$EVQzFV)CvN-BFEQ*U`_b3TM5
zAaZ32{jxEIV?{J|s|V-&KI}qZ9Fc4#B+$Ta%H&|N&gH&9^f@Y(amr*Ri3^7;u*)Uz
z?3KV6mBifYzlD$@F{Gs
zYW0weFq$z)F$%7{5KDMCuLBFm>CBTyV9dw&KO8Yd*aqS=pS;-Gda=7XFH*WR*sE#*
zhluWQ&?jex8)Mid4aPbLwRr*9d}hLdTbqh~6I5J4KVG7fSda9G@s{a#$U&8}4C;h~
z_Yeor1~3#kO6Q&8IaLx+F(h*HviQF*!cL``QFmqrMu{8
z3WkCsHa!VRT0~D?GE-6QmVN)5puR5sUxV{ELg}d(T`rA(En8cz*^EM+3EtuXw+baF
zO{L(rz|rQ_peJ?<(ydRIDpMk;+
zCvwUAXyBYqPgJ@k5cvRFx1_g9wOh(9F39x_R#X2|Kr`TuBH1pucGY6X%}~uDEP1e6
zP~H`Qsa~Bjsou!%hkclt5y{)29<{JXs&Jizgh?~-a>mf!1{WuyB2`rjjFJUsli`;6
zBXG6hD6l0J|LWohKK1|^?t$72tEpnF)(bjKQh|$ZUlg5Bs3?6XE2Z#ZeW{gW(uk2~*wRq9ZQ7tb3uNCM0q8a38v_WGD$`zggdD
z$R^J1^byjV_`Zso`?!a>kQ&QtlfC7U=pvIKuK^n8vO;^FaV?wxxvuj%T7s2;sDKVO
zS|$^fj@76F|N7$_nB|{BfgV_QGp)dQiAvehQk5lI9t7c-$*2
z73)@BiGzcsRMh}3tL(l_Tv8=G){(#KJr)%E<-KvU_2Ann%Q3*nE|kCsDLA09Q{6Xi
zir#7SN*vH&htWS+2uyfms!edsU(~KHvp_RscjQE_s=zXr<9*(0K)lDEP#ZR)J00+!
z8h>jRY#87(gE&UahwlYU<2)?s$D$wGl2^gY$hCFI^{jr=D%ffV2Z-sGvEfV&np57k
z{#O-vI!D&{ua%l-7z8?^(Sv!5##Q0rZLXjig}JUFXhX_DW(`DqX1iP&P_0odq4^iO*my0VKZ89kDLGb7=
zL6)+l5;7AG`-!#W2#CN-=4MFl2UZ%#sw6gAc4BHO@*X)xH0RjtY4tG^5Wi)wMa`=<
z0Ap<{#nnk57iWhdOyd_`O3TE|Xk9#6-P{W2n#W7enp^L1%sx-aQ`&(UwjP%I3KaUe
z7l7-Kz}%++BDsG8eAthyuYk0Az2XR;QYtUszGFLZX=9=nV8lEM^u*
z_TO&!B4q7rwXHD%7k0TN*wv7aV|2!3B={>njMg+rA;21?yBJUrfY5bgVCEq>47#hs
z8`*G@bA-bI*lC=%^r?-o$9F&oFy$U*D9>w2)G?Znf&|gjC@JNwIrJ
zQUbAoq?Dzx{wGB7)S8~Y@&T0XR~;cp_BlV5hsb=ZiAbUGQ_Va3p=ktPU%>mZi;}Um
zaTkcZL`h8!kMD-_EcJw+H3YW{Ox{TBOSoyIhoZ812S)eYx3t60^eq@8==gJA+e4ba
zrj1R#Y3^%zG|RVLSkaJTRLoJuRb8;MIj)vm@AA=6cv&0=`$HGHMqTO|96{_oI;BA>
z*H8X!p`+rAaCNU;K`pq2Tz-TZCyr+)c&3W~;Snq)a?Qe@Gu+
zu=f^O&zY>e(0Ar#+P`rWftvg*;R{Pp*;AH8Q8cm37ALFij;vjZhr&d0*2UAK9~CBN
zeW?LI?aD3_^Y7IYE>MdchCUSE8TnHjY|IUj@Erj*OlhX~HlIkGh2A
z>Jq$T`&j;vL%+KJ}{7z9JiH^3kZWHd%FHh<80owLd@-pq*oxmC$ZDRjVOJ6K~PS~7x&ydk~serY4p0)yebIm^5HdaGEKLg+{Y5&Zq>wI*{0tLKy
zpK$64b%7JFmP`?d6KOAiQ!Fi}X89fNHZS;5IUc`=JK|$}30v}?