diff --git a/.ci.sh b/.ci.sh deleted file mode 100644 index 40b8127ce8..0000000000 --- a/.ci.sh +++ /dev/null @@ -1,586 +0,0 @@ -#!/bin/sh - -: "${npm_config_fast:=}" - -SQLMATH_CFLAG_WALL_LIST=" \ - -Wall \ - -Werror \ - -Wextra \ -" -SQLMATH_CFLAG_WNO_LIST=" \ - -Werror \ - -Wno-all \ - -Wno-extra \ -" - -shCiArtifactUploadCustom() {(set -e -# This function will run custom-code to upload build-artifacts. - git fetch origin artifact - git checkout origin/artifact "branch-$GITHUB_BRANCH0" - mv "branch-$GITHUB_BRANCH0"/* . - git add -f sqlmath_wasm.* - # screenshot html - PID_LIST="" - shBrowserScreenshot \ - "https://$GITHUB_GITHUB_IO/branch-$GITHUB_BRANCH0/index.html" & - PID_LIST="$PID_LIST $!" - shBrowserScreenshot .artifact/apidoc.html & - PID_LIST="$PID_LIST $!" - shPidListWait screenshot "$PID_LIST" -)} - -shCiBaseCustom() {(set -e -# This function will run custom-code for base-ci. - shCiEmsdkExport - FILE_LIB_LGBM="$(node --input-type=module -e ' -function libPlatformArchExt() { - let libArch = process.arch; - let libExt = process.platform; - let libPlatform = process.platform; - libExt = libExt.replace("darwin", "dylib"); - libExt = libExt.replace("win32", "dll"); - libExt = libExt.replace(libPlatform, "so"); - return `${libPlatform}_${libArch}.${libExt}`; -} -process.stdout.write(`lib_lightgbm_${libPlatformArchExt()}`); -' "$@")" # ' - # bugfix - Library not loaded: /usr/local/opt/libomp/lib/libomp.dylib - if [ ! -f "sqlmath/$FILE_LIB_LGBM" ] - then - case "$(uname)" in - Darwin*) - brew install libomp - cp -L "$(brew --prefix libomp)/lib/libomp.dylib" \ -"sqlmath/$(node -p '`libomp_${process.platform}_${process.arch}.dylib`')" - ;; - esac - pip install lightgbm=="$(printf "v4.6.0" | sed "s|v||")" - cp "$( - find "$( - pip show lightgbm | grep Location | sed "s|Location: ||" - )/lightgbm" | grep "\/dev/null) - then - sudo apt-get update - sudo apt-get install -y graphicsmagick - fi - # mkdir .artifact/ - mkdir -p .artifact/ - shImageLogoCreate - shCiBuildWasm "" - # .github_cache - save - if [ "$GITHUB_ACTION" ] && [ ! -d .github_cache/_emsdk/ ] - then - mkdir -p .github_cache/ - cp -a "$EMSDK" .github_cache/ - fi - fi - ) & - PID_LIST="$PID_LIST $!" - # - shPidListWait shCiBaseCustom "$PID_LIST" - # - # upload artifact - if shCiMatrixIsmainNodeversion && \ - { \ - [ "$GITHUB_EVENT_NAME" = push ] || \ - [ "$GITHUB_EVENT_NAME" = schedule ] || \ - [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; \ - } && \ - { \ - [ "$GITHUB_BRANCH0" = alpha ] || \ - [ "$GITHUB_BRANCH0" = beta ] || \ - [ "$GITHUB_BRANCH0" = master ]; \ - } - then - export GITHUB_UPLOAD_RETRY=0 - while true - do - export GITHUB_UPLOAD_RETRY="$((GITHUB_UPLOAD_RETRY + 1))" - if [ ! "$GITHUB_UPLOAD_RETRY" -le 4 ] - then - return 1 - fi - if (node --input-type=module --eval ' -import moduleChildProcess from "child_process"; -(function () { - moduleChildProcess.spawn( - "sh", - ["jslint_ci.sh", "shCiBaseCustomArtifactUpload"], - {stdio: ["ignore", 1, 2]} - ).on("exit", process.exit); -}()); -' "$@") # ' - then - break - fi - sleep 5 - done - fi -)} - -shCiBaseCustomArtifactUpload() {(set -e -# This function will upload build-artifacts to branch-gh-pages. - COMMIT_MESSAGE="- upload artifact -- retry$GITHUB_UPLOAD_RETRY -- $GITHUB_BRANCH0 -- $(printf "%s" "$GITHUB_SHA" | cut -c-8) -- $(uname) -" - printf "\n\n%s\n" "$COMMIT_MESSAGE" - # init .git/config - git config --local user.email "github-actions@users.noreply.github.com" - git config --local user.name "github-actions" - # git clone origin/artifact - rm -rf .tmp/artifact/ - shGitCmdWithGithubToken clone origin .tmp/artifact \ - --branch=artifact --single-branch - ( - cd .tmp/artifact/ - cp ../../.git/config .git/config - # update dir branch-$GITHUB_BRANCH0 - mkdir -p "branch-$GITHUB_BRANCH0/" - ( - cd "branch-$GITHUB_BRANCH0/" - rm -f lib_lightgbm.* - rm -f libomp.* - case "$(uname)" in - Darwin*) - rm -f ./*darwin.so - case $(uname -m) in - arm64) - rm -f ./*darwin*arm64* - rm -f ./*macos*arm64* - ;; - x86_64) - rm -f ./*darwin*x64* - rm -f ./*macos*x86_64* - ;; - esac - # save libomp - cp ../../../sqlmath/libomp* ./ - ;; - Linux*) - rm -f ./*linux* - # save sdist - rm -f ./*.tar.gz - cp ../../../dist/sqlmath-*.tar.gz ./ - # save wasm - rm -f sqlmath_wasm* - cp ../../../.artifact/asset_image_logo_256.png ./ - cp ../../../sqlmath_wasm* ./ - ;; - MINGW*) - rm -f ./*win32_x64* - rm -f ./*win_amd64* - ;; - esac - cp ../../../_sqlmath.napi* ./ - cp ../../../_sqlmath.shell* ./ - cp ../../../dist/sqlmath-*.whl ./ - cp ../../../sqlmath/lib_lightgbm* ./ - rm -f ./*win32_x64*.exp - rm -f ./*win32_x64*.lib - ) - # git commit - git add . - git add -f "branch-$GITHUB_BRANCH0"/_sqlmath* - if (git commit -am "$COMMIT_MESSAGE") - then - # git push - shGitCmdWithGithubToken push origin artifact - # git squash - if shCiMatrixIsmainName && \ - { \ - [ "$GITHUB_BRANCH0" = alpha ] || \ - [ "$GITHUB_BRANCH0" = beta ] || \ - [ "$GITHUB_BRANCH0" = master ]; \ - } - then - shGitCommitPushOrSquash "" 50 - fi - fi - # debug - shGitLsTree - ) -)} - -shCiBuildWasm() {(set -e -# This function will build binaries in wasm. - shCiEmsdkExport - # install emsdk - shCiEmsdkInstall - # cd ${EMSDK} && . ./emsdk_env.sh && cd .. - # build wasm - printf "shCiBuildWasm\n" 1>&2 - OPTION1="$OPTION1 -flto" - # debug - # OPTION1="$OPTION1 -O0" - OPTION1="$OPTION1 -Os" - # OPTION1="$OPTION1 -Oz" - # OPTION1="$OPTION1 -fsanitize=address" - for FILE in \ - sqlmath_base.c \ - sqlmath_external_sqlite.c - do - OPTION2="" - case "$FILE" in - sqlmath_base.c) - OPTION2="$OPTION2 -DSRC_SQLMATH_BASE_C2=" - ;; - sqlmath_external_sqlite.c) - OPTION2="$OPTION2 -DSRC_SQLITE_BASE_C2=" - ;; - esac - FILE2="build/$(basename "$FILE").wasm.o" - OPTION2="$OPTION2 -c $FILE -o $FILE2" - case "$FILE" in - sqlmath_base.c) - OPTION2="$OPTION2 $SQLMATH_CFLAG_WALL_LIST" - ;; - *) - OPTION2="$OPTION2 $SQLMATH_CFLAG_WNO_LIST" - # optimization - skip rebuild of rollup if possible - if [ -n "$(find "$FILE2" -prune -newer "$FILE" 2>/dev/null)" ] - then - printf "shCiBuildWasm - skip %s\n" "$FILE" 1>&2 - continue - fi - esac - # shellcheck disable=SC2086 - emcc $OPTION1 $OPTION2 - done - OPTION2="" - # - OPTION2="$OPTION2 -s EXPORTED_FUNCTIONS=_sqlite3_initialize" - OPTION2="$OPTION2,_dbCall" - OPTION2="$OPTION2,_dbFileLoadOrSave" - OPTION2="$OPTION2,_jsbatonGetErrmsg" - OPTION2="$OPTION2,_jsbatonGetInt64" - OPTION2="$OPTION2,_jsbatonGetString" - OPTION2="$OPTION2,_sqlite3_errmsg" - OPTION2="$OPTION2,_sqlite3_free" - OPTION2="$OPTION2,_sqlite3_malloc" - # - OPTION2="$OPTION2 -s EXPORTED_RUNTIME_METHODS=cwrap" - OPTION2="$OPTION2 -s LLD_REPORT_UNDEFINED" - # - case "$1" in - --debug) - OPTION2="$OPTION2 -s ASSERTIONS=1 -s SAFE_HEAP=1" - ;; - *) - OPTION2="$OPTION2 --closure 1" - ;; - esac - # shellcheck disable=SC2086 - emcc $OPTION1 $OPTION2 \ - --memory-init-file 0 \ - --pre-js sqlmath_wrapper_wasm.js \ - -o build/sqlmath_wasm.js \ - -s ALLOW_MEMORY_GROWTH=1 \ - -s ALLOW_TABLE_GROWTH=1 \ - -s NODEJS_CATCH_EXIT=0 \ - -s NODEJS_CATCH_REJECTION=0 \ - -s RESERVED_FUNCTION_POINTERS=64 \ - -s SINGLE_FILE=0 \ - -s USE_ZLIB=1 \ - -s WASM=1 \ - -s WASM_BIGINT \ - build/sqlmath_base.c.wasm.o \ - build/sqlmath_external_sqlite.c.wasm.o \ - # - printf '' > sqlmath_wasm.js - { - printf "/*jslint-disable*/ -// Copyright (c) 2021 Kai Zhu -// SPDX-License-Identifier: MIT -// %s -(function () { -\"use strict\"; -" "$(date -u +"%Y-%m-%dT%H:%M:%S%z")" - tr -d "\r" < build/sqlmath_wasm.js - printf ' -}()); -/*jslint-enable*/ -' - } >> sqlmath_wasm.js - cp build/sqlmath_wasm.wasm ./ - ls -l sqlmath_wasm.* -)} - -shCiEmsdkExport() { -# This function will export emsdk env. - export EMSCRIPTEN_VERSION=3.1.3 - export EMSDK="$PWD/_emsdk" - # https://github.com/sql-js/sql.js/blob/v1.6.2/.devcontainer/Dockerfile - if [ ! "$PATH_EMSDK" ] - then - export PATH_EMSDK="$EMSDK:$EMSDK/upstream/emscripten/" - export PATH="$PATH_EMSDK:$PATH" - fi -} - -# shellcheck disable=SC2086 -shCiEmsdkInstall() {(set -e -# This function will install emsdk. - shCiEmsdkExport - if [ -d "$EMSDK" ] - then - exit - fi - # https://github.com/emscripten-core/emsdk/blob/3.1.74/docker/Dockerfile - git clone https://github.com/emscripten-core/emsdk.git $EMSDK - # - echo "## Install Emscripten" - cd ${EMSDK} - ./emsdk install ${EMSCRIPTEN_VERSION} - echo "## Done" - # - # This generates configuration that contains all valid paths according to - # installed SDK - # TODO(sbc): We should be able to use just emcc -v here but it doesn't - # currently create the sanity file. - cd ${EMSDK} - echo "## Generate standard configuration" - ./emsdk activate ${EMSCRIPTEN_VERSION} - chmod 777 ${EMSDK}/upstream/emscripten - chmod -R 777 ${EMSDK}/upstream/emscripten/cache - echo "int main() { return 0; }" > hello.c - ${EMSDK}/upstream/emscripten/emcc -c hello.c \ - -s USE_LIBPNG=1 \ - -s USE_ZLIB=1 - cat ${EMSDK}/upstream/emscripten/cache/sanity.txt - echo "## Done" - # - # Cleanup Emscripten installation and strip some symbols - echo "## Aggressive optimization: Remove debug symbols" - cd ${EMSDK} && . ./emsdk_env.sh - # Remove debugging symbols from embedded node (extra 7MB) - # strip -s `which node` - # Tests consume ~80MB disc space - rm -fr ${EMSDK}/upstream/emscripten/tests - # strip out symbols from clang (~extra 50MB disc space) - find ${EMSDK}/upstream/bin -type f -exec strip -s {} + || true - echo "## Done" - # - # download ports - # touch "$EMSDK/.null.c" - # emcc \ - # -s USE_LIBPNG=1 \ - # -s USE_ZLIB=1 \ - # "$EMSDK/.null.c" -o "$EMSDK/.null_wasm.js" -)} - -shIndentC() {(set -e -# This function will indent/prettify c file. - if (uname | grep -q "MING\|MSYS") - then - ./indent.exe \ - --blank-lines-after-commas \ - --braces-on-func-def-line \ - --break-function-decl-args \ - --break-function-decl-args-end \ - --dont-line-up-parentheses \ - --k-and-r-style \ - --line-length78 \ - --no-tabs \ - -bfde \ - "$@" - dos2unix "$@" - fi -)} - -shCiLintCustom() {(set -e -# This function will run custom-code to lint files. - if [ "$GITHUB_ACTION" ] - then - pip install pycodestyle ruff - fi - shLintPython \ - setup.py \ - sqlmath/__init__.py \ - test.py -)} - -shCiPublishNpmCustom() {(set -e -# This function will run custom-code to npm-publish package. - # fetch artifact - git fetch origin artifact --depth=1 - git checkout origin/artifact branch-beta/ - cp -a branch-beta/_sqlmath.napi* ./ - cp -a branch-beta/_sqlmath.shell* ./ - cp -a branch-beta/sqlmath_wasm.* ./ - mkdir -p sqlmath/ - cp -a branch-beta/lib_lightgbm* sqlmath/ - # npm-publish - npm publish --access public -)} - -shCiPublishPypiCustom() {(set -e -# This function will run custom-code to npm-publish package. - # fetch artifact - git fetch origin artifact --depth=1 - git checkout origin/artifact branch-beta/ - mkdir dist/ - cp -a branch-beta/sqlmath-*.tar.gz dist/ - cp -a branch-beta/sqlmath-*.whl dist/ - ls -la dist/ -)} - -shCiTestNodejs() {(set -e -# This function will run test in nodejs. - # init .tmp - mkdir -p .tmp/ - # rebuild c-module - export npm_config_mode_test=1 - if [ "$npm_config_fast" != true ] - then - # lint c-file - python cpplint.py \ - --filter=-whitespace/comments \ - sqlmath_base.c - # lint js-file - node jslint.mjs . - # create file MANIFEST.in - # git ls-tree -r --name-only HEAD | sed "s|^|include |" > MANIFEST.in - if [ -d .git/ ] - then - git ls-tree -r --name-only HEAD | sed "s|^|include |" > MANIFEST.in - fi - # create PKG-INFO - python setup.py build_pkg_info - # build nodejs c-addon - PID_LIST="" - ( - unset npm_config_mode_test - npm_config_mode_setup=1 node --input-type=module -e ' -import {ciBuildExt} from "./sqlmath.mjs"; -ciBuildExt({process}); -' # ' - ) & - PID_LIST="$PID_LIST $!" - # build python c-extension - if [ ! "$npm_config_mode_test_nopython" ] - then - python setup.py build_ext & - PID_LIST="$PID_LIST $!" - fi - shPidListWait build_ext "$PID_LIST" - fi; - PID_LIST="" - # test nodejs - ( - rm -f ./*~ .test*.sqlite __data/.test*.sqlite - COVERAGE_EXCLUDE="$COVERAGE_EXCLUDE --exclude=jslint.mjs" - if (node --eval ' -require("assert")(require("./package.json").name !== "sqlmath"); -' >/dev/null 2>&1) - then - COVERAGE_EXCLUDE="$COVERAGE_EXCLUDE --exclude=sqlmath.mjs" - fi - NODE_TEST_OPTION="$NODE_TEST_OPTION --trace-uncaught --trace-warnings" - # shellcheck disable=SC2086 - shRunWithCoverage $COVERAGE_EXCLUDE node $NODE_TEST_OPTION test.mjs - ) & - PID_LIST="$PID_LIST $!" - # test python - if [ ! "$npm_config_mode_test_nopython" ] - then - python setup.py test & - PID_LIST="$PID_LIST $!" - fi - shPidListWait test "$PID_LIST" -)} - -shSqlmathUpdate() {(set -e -# This function will update files with ~/Documents/sqlmath/. - . "$HOME/myci2.sh" && shMyciUpdate - if [ "$PWD/" = "$HOME/Documents/sqlmath/" ] - then - DIR=sqlite-autoconf-3500400 - URL=https://www.sqlite.org/2025/sqlite-autoconf-3500400.tar.gz - # shRollupFetch - if [ ! -d ".$DIR" ] - then - printf "%s %s\n" ".$DIR" "$URL" - curl -L "$URL" | tar -xz - rm -rf ".$DIR" - mv "$DIR" ".$DIR" - fi - shRollupFetch asset_sqlmath_external_rollup.js - shRollupFetch index.html - shRollupFetch sqlmath_base.h - shRollupFetch sqlmath_external_sqlite.c - # shIndentC - if (uname | grep -q "MING\|MSYS") - then - shIndentC sqlmath_base.c - shIndentC sqlmath_base.h - shIndentC sqlmath_external_sqlite.c - fi - return - fi - if [ -d "$HOME/Documents/sqlmath/" ] - then - for FILE in \ - .ci.sh \ - asset_sqlmath_external_rollup.js \ - cpplint.py \ - index.html \ - setup.py \ - sqlmath.mjs \ - sqlmath/__init__.py \ - sqlmath_base.c \ - sqlmath_base.h \ - sqlmath_browser.mjs \ - sqlmath_external_sqlite.c \ - sqlmath_wrapper_wasm.js \ - zlib.v1.3.1.vcpkg.x64-windows-static.lib - do - ln -f "$HOME/Documents/sqlmath/$FILE" "$FILE" - done - fi - if (command -v shSqlmathUpdate2 >/dev/null) - then - shSqlmathUpdate2 - fi - git --no-pager diff -)} diff --git a/.gitconfig b/.gitconfig deleted file mode 100644 index 4f51e580f3..0000000000 --- a/.gitconfig +++ /dev/null @@ -1,30 +0,0 @@ -# base - .gitconfig - beg -[branch "alpha"] - merge = refs/heads/alpha - remote = origin -[branch "alpha2"] - merge = refs/heads/alpha2 - remote = origin -[branch "base"] - merge = refs/heads/base - remote = origin -[core] - # autocrlf = false - autocrlf = input - bare = false - # filemode = false - logallrefupdates = true - repositoryformatversion = 0 -[diff] - algorithm = histogram -[pull] - ff = only -[receive] - denyCurrentBranch = warn -# base - .gitconfig - end -[remote "origin"] - fetch = +refs/heads/*:refs/remotes/origin/* - url = https://github.com/user/sqlmath -[remote "upstream"] - fetch = +refs/heads/*:refs/remotes/upstream/* - url = https://github.com/sqlmath/sqlmath diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 31b30c6c31..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,102 +0,0 @@ -# this workflow will run nodejs coverages and tests -# and upload build-artifacts to branch-gh-pages -name: ci -on: - pull_request: - branches: - - alpha - - beta - - master - push: - branches: - - alpha - - beta - - master - workflow_dispatch: -permissions: - contents: write -jobs: - - - job1: - strategy: - matrix: - architecture: - # - arm64 - - x64 - # - x86 - node_version: - - "24" - os: - - macos-15 - - macos-15-intel - - ubuntu-22.04 - - windows-2022 - python_version: - - "3.12" -# base - .github/workflows/ci.yml - beg - env: - CI_MATRIX_NAME: > - node - v${{ matrix.node_version }} - ${{ matrix.architecture }} - ${{ matrix.os }} - CI_MATRIX_NAME_MAIN: "node v24 x64 ubuntu-22.04" - CI_MATRIX_NODE_VERSION: ${{ matrix.node_version }} - CI_MATRIX_NODE_VERSION_MAIN: "24" - CI_WORKFLOW_NAME: > - ${{ github.workflow }} - - ${{ github.event_name }} - - ${{ github.event.inputs.workflow_dispatch_name }} - - ${{ github.ref_name }} - name: > - node - v${{ matrix.node_version }} - ${{ matrix.architecture }} - ${{ matrix.os }} - runs-on: ${{ matrix.os }} - steps: - # disable autocrlf in windows - - run: | - sh -c "uname -a" - echo "$(date -u +"%Y-%m-%d %TZ") - ${{ env.CI_WORKFLOW_NAME }}" # " - git config --global core.autocrlf false - # https://github.com/actions/checkout - - uses: actions/checkout@v5 - # https://github.com/actions/cache - - uses: actions/cache@v5 - with: - key: > - ${{ hashFiles('./package.json') }} - ${{ matrix.architecture }} - ${{ matrix.node_version }} - ${{ matrix.os }} - path: .github_cache/ - # fetch jslint_ci.sh from trusted source - - run: | - git fetch origin alpha --depth=1 - sh -c 'for FILE in .ci.sh .ci2.sh jslint_ci.sh myci2.sh; do - if [ -f "$FILE" ]; then git checkout origin/alpha "$FILE"; fi; done' - # pre-run .ci.sh - - run: sh jslint_ci.sh shCiPre - if: > - github.event_name == 'push' || - github.event_name == 'schedule' || - github.event_name == 'workflow_dispatch' - # https://github.com/actions/setup-node - - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node_version }} - # https://github.com/actions/setup-python - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python_version }} - # run nodejs coverages and tests - - run: sh jslint_ci.sh shCiBase - # upload build-artifacts to branch-gh-pages - - run: sh jslint_ci.sh shCiArtifactUpload - if: > - github.event_name == 'push' || - github.event_name == 'schedule' || - github.event_name == 'workflow_dispatch' -# base - .github/workflows/ci.yml - end diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 79fdebcaad..0000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,69 +0,0 @@ -# base - .github/workflows/publish.yml - beg -# Publishing packages to npm and GitHub Packages -# https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages#publishing-packages-to-npm-and-github-packages -# https://docs.npmjs.com/trusted-publishers#step-2-configure-your-cicd-workflow -name: publish -on: - release: - types: [created] - workflow_dispatch: -jobs: - - - publish_github: - name: Publish Package to GitHub - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v6 - with: - node-version: "24" - registry-url: "https://npm.pkg.github.com" - # Defaults to the user or organization that owns the workflow file - # scope: "@octocat" - # Publish to GitHub Packages - - run: sh jslint_ci.sh shCiPublishNpm - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_REGISTRY: github - - - publish_npmjs: - name: Publish Package to npmjs - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write # Required for OIDC - packages: write - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v6 - with: - node-version: "24" - registry-url: "https://registry.npmjs.org" - # Publish to npmjs Packages - - run: sh jslint_ci.sh shCiPublishNpm -# base - .github/workflows/publish.yml - end - - - publish_pypi: - name: Publish Package to PyPI - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/project/sqlmath/ - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - # retrieve your distributions here - - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - run: sh jslint_ci.sh shCiPublishPypi - # publish to pypi - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish_pypi_test.yml b/.github/workflows/publish_pypi_test.yml deleted file mode 100644 index e26d1ec82e..0000000000 --- a/.github/workflows/publish_pypi_test.yml +++ /dev/null @@ -1,30 +0,0 @@ -# https://github.com/pypa/gh-action-pypi-publish -name: publish_pypi_test -on: - # release: - # types: [created] - workflow_dispatch: -jobs: - - - publish_pypi: - name: Publish Package to PyPI - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/project/sqlmath/ - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - # retrieve your distributions here - - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - run: sh jslint_ci.sh shCiPublishPypi - # publish to pypi - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - verbose: true diff --git a/.gitignore b/.gitignore index 63c898e3dc..8b13789179 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1 @@ -# base - .gitignore - beg -"* -'* -*.[0123456789][0123456789] -*.lock -*.log -*.tgz -*~ -.* -_* -temp* -tmp/ -undefined -# nodejs -build/ -node_modules/ -package-lock.json - -# python -*.egg-info/ -*.py[cod] -dist/ -htmlcov/ -wheelhouse/ - -# vscode -*.vsix - -# ! -!.gitconfig -!.github -!.gitignore -!.npmignore -!_*.py -# base - .gitignore - end - -binding.gyp -lib_lightgbm* -libomp* -sqlmath_wasm.* diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 4c707f58ad..0000000000 --- a/.npmignore +++ /dev/null @@ -1,17 +0,0 @@ -* -.* -node_modules - -!.npmignore -!CHANGELOG.md -!LICENSE -!README.md - -!_sqlmath.napi* -!_sqlmath.shell* -!jslint.mjs -!sqlmath.mjs -!sqlmath/lib_lightgbm* -!sqlmath_browser.mjs -!sqlmath_wasm* -!test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7bc8c02655..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,854 +0,0 @@ -# Changelog - -# Todo -- sqlmath - Update function dbTableImportAsync() with excel/xlsx support. -- sqlmath - Add file asset_sheetjs_rollup.mjs. -- sqlmath - Streamline and optimize sql-function WIN_SINEFIT2(). -- sqlite - Add similar error-handling from builtin-sql-function PERCENTILE() into custom-sql-function QUANTILE(). -- none - -# v2026.7.30 -- jslint-ecma - Add ES2015-feature for..of. -- jslint - Change scope from scope_function to scope_block: - - const-declaration - - let-declaration - - function-declaration -- jslint - Expand built-in-globals for browser, ecma, and node - auto-generated from online-sources. -- jslint-ci - Add automated ci for shellcheck to lint shell-scripts. -- jslint-regression - Cleanup indent for multiline-method-chaining. -- jslint-regression - Fix long-running regression where 'let x = x;' doesn't warn about temporal-dead-zone. - -# v2026.6.30 -- jslint-ecma - Update README.md, documenting supported ES2015+ features. -- jslint-ecma - Unify ES2015-destructure-logic into function prefix_destructure(). -- jslint - Wrap all property-updates 'name.init = true/false' with calls to: - name_lookup() - 'aa=0' - name_push() - 'let aa=0' -- cpplint - Upgrade to cpplint@v2.0.2. -- jslint - Add ES2018-syntax for object-literal-spread-operator. -- jslint - Add ES2025-syntax 'import ... with {...}'. - -# v2026.5.31 -- sqlmath - Add function csvFromListofList(). -- sqlmath - Rename function jsonRowListFromCsv() to csvToListofList(). -- sqlmath-demo - Update chart tradebot_tech_intra_xxx normalized to spy_prc instead of stk_pct. -- sqlmath - Add function uvthreadpoolsizeGet(). - -# v2026.4.31 -- sqlmath-npm - bugfix - Fix missing lib_lightgbm pre-built libraries in npm-package. - part3 - ./sqlmath/lib_lightgbm_xxx. -- sqlmath-npm - bugfix - Fix missing lib_lightgbm pre-built libraries in npm-package. - part2 - git checkout origin/artifact branch-beta/ - -# v2026.4.30 -- sqlmath-python - Add context manager and Pythonic API (execute, close) to SqlmathDb matching sqlite3 conventions. -- sqlmath-python - Add __repr__ and __bool__ methods to SqlmathDb for better debugging experience. -- sqlmath - Remove under-used custom-files sqlmath_custom.c, sqlmath_custom.mjs. -- github-ci - Pin various github-runner-os to stable/lts version. -- sqlmath-demo - Change historical charts from 5-years to 3-years. -- sqlmath-npm - bugfix - Fix missing lib_lightgbm pre-built libraries in npm-package. -- sqlmath-doc - Rewrite README with examples, API reference, use cases. - -# v2026.3.31 -- python-ci - bugfix - Fix shell-function shCiBaseCustom() always skipping python setup.py build_ext. -- ci - Speed-up windows-ci by replacing windows-latest with windows-2022. -- sqlmath-ci - Speed-up ci with env-var npm_config_mode_test_nopython, allowing shell-function shCiTestNodejs() to be run in background, parallel to python -m cibuildwheel. -- sqlmath-ci - Rename pre-built-binaries lib_lightgbm.xxx, libomp.xxx to lib_lightgbm_platform_arch.xxx, libomp_platform_arch.xxx, to prevent name-collision under darwin_arm64 and darwin_x64 builds. -- sqlmath-ci - Update file setup.py to open text-files with utf-8 encoding. -- sqlmath-ci - Update file .github/workflows/ci.yml with extra matrix.os macos-15-intel. -- jslint-ci - Update shell-function shGitPullrequestCleanup() to allow squash-and-merge pull-request. -- sqlmath-ci - Update file .github/workflows/ci.yml with hook to run on pull-request. - -# v2026.3.1 -- sqlmath-python - bugfix - Fix 762-character-limit SQL-string-bug in python-function db_exec(). -- jslint-ci - Update shell-function shLintPython(). -- python - Auto-detect-and-load lib_lightgbm.dll. -- sqlite - Add sqlite-extension csv.c, tsv.c. - -# v2026.2.28 -- python-ci - Fix ruff lint-errors. -- jslint-ci - Update shell-function shCiBase() to check npm-version-support, before running npm-pkg-fix. - -# v2026.1.31 -- demo - Replace sector and subsector charts with subindustry chart. -- chart - Make tooltip more transparent, so datapoints behind it are more visible. -- chart - Cleanup chart _15_tradebot_tech_intra_1_month. -- chart - Add intraday-technical-indicator tradebot_tech_intra.spy_zcl. -- chart - Change time-scale of 1-month charts from 1-day to 15-minute/5-minute. - -# v2025.12.28 -- jslint - Upgrade to jslint-v2025.12.28. -- sqlmath - Update function dbOpenAsync() with default-prm timeoutBusy=5000. -- ci - Upgrade nodejs used in ci to v24. - -# v2025.9.30 -- sqlmath - Update function dbExecProfile() with additional-arg sqlLength. -- sqlmath - Update function dbOpenAsync() with default-filename :memory:. -- coverage - Add coverage-directives '/*coverage-disable*/', '/*coverage-enable*/'. '//coverage-ignore-line'. -- sqlmath - Add function dbExecProfile() to profile sql-queries. -- sqlmath - Update function dbCallAsync() to include db.filename in error-message. -- sqlmath - Replace miniz-library with native-zib-library. -- sqlmath - ugly-hack - win32-sqlite-shell doesn't like nodejs-builtin-zlib, so link with external-zlib. -- sqlmath - Replace sql-functions ZLIB_COMPRESS() to GZIP_COMPRESS(), ZLIB_UNCOMPRESS() to GZIP_UNCOMPRESS(). -- sqlmath - Prefer platform-native zlib over sqlmath_external_sqlite.c. -- sqlmath - bugfix - Fix sql-functions ZLIB_COMPRESS(), ZLIB_UNCOMPRESS() crashing sqlite-shell-executable. -- ml - Update sql-function LGBM_TRAINFROMTABLE() to use C_API_FEATURE_IMPORTANCE_GAIN instead of C_API_FEATURE_IMPORTANCE_SPLIT. -- sqlmath - Update function win_sinefit() to rename outputs from/to xx0/xxa, xx1/xxb, yy0/yya, yy1/yyb, rr0/rra, rr1/rrb. -- sqlmath - Update sql-function SINEFIT_REFITLAST() to create copy of wsf-blob, so original argument is not mutated. -- sqlmath - Update function win_sinefit() to allow outputting prm grr, mrr, vrr. - -# v2025.8.30 -- sqlite - Upgrade to sqlite-v3.50.4. -- python - Add support for PEP 703 – Making the Global Interpreter Lock Optional in CPython. - -# v2025.6.28 -- sqlite - Upgrade to sqlite-v3.50.2. -- sqlmath - Update function dbExecAsync() with extra param modeNoop, used in mock-code-coverage. - -# v2025.5.31 -- sqlite - Upgrade to sqlite-v3.49.2. -- betadog - Add table a_lgbm_intra, and prm a_state.zcis_spx. -- lgbm - Upgrade to lightgbm-v4.6.0. -- jslint - Upgrade to jslint-v2025.3.31. - -# v2025.3.31 -- ubuntu-ci - bugfix - Fix out-of-date apt-list when installing graphicsmagick. -- sqlite - Upgrade to sqlite-v3.49.1. -- Cleanup sql-functions ZLIB_COMPRESS(), ZLIB_UNCOMPRESS(). - -# v2025.2.28 -- sqlmath - Update sql-function SHA256() to use tinycrypt. -- jslint - Update shell-function shDirHttplinkValidate() with pragma <\!!--novalidate--\>. -- ci - Upgrade cpplint.py used in ci to v2.0.0. -- sqlite - Rename custom-sql-function MEDIAN() to MEDIAN2(). -- sqlite - Enable builtin-sql-functions MEDIAN(), PERCENTILE(), PERCENTILE_CONT(), PERCENTILE_DISC(). -- sqlite - Upgrade to sqlite-v3.47.2. -- ci - Upgrade nodejs used in ci to v22. - -# v2025.1.31 -- webdemo - Update coloring to highlight tqq/sqq stocks. -- sqlmath - Revamp sql-function win_sinefit2() to guess frequency from stdev of tt, instead of dft. - -# v2024.12.25 -- ci - Auto-create asset_image_logo_256.png from asset_image_logo_256.html. -- ci - Fix shell-function shRollupFetch() from making excessive github-api-request. -- ci - Fix improperly-cropped jslint-logo, auto-generated by headless-chrome. -- ci - Fix failed-ci from missing graphicsmagick library in latest Ubuntu image. -- betadog-revamp - Remove trading-logic for s_sqq=-1. -- sqlmath - Add sql-functions SHA256(), STRTOLL(). - -# v2024.11.24 -- sqlmath - Streamline idate-sql-functions to just: - IDATEADD() - IDATEFROM() - IDATEFROMEPOCH() - IDATETO() - IDATETOEPOCH() - IDATEYMDFROM() - IDATEYMDFROMEPOCH() -- jslint - Upgrade to jslint-v2024.11.24. -- webdemo - bugfix - Fix scaling-bug for technical-prm tradebot_technical.all.1e_stk_pnl. - -# v2024.10.23 -- sqlite - Upgrade to sqlite-v3.46.1. -- ci - Update shell-function shHttpFileServer() to auto-serve /index.html, when url-path is root /. -- sqlmath - Expand sql-function IDATEFROMTEXT() to allow shorthand IDATEFROMTEXT(...) for IDATEFROMTEXT(DATE(...)). - -# v2024.9.30 -- lgbm - Replace Microsoft-release-version with PyPi-version of lgbm-binary, because it includes gpu-extension. -- sqlmath - Add sql-function WIN_AVG1(), WIN_AVG2(). - -# v2024.8.30 -- betadog - Re-introduce trading of sp500 stocks. -- lgbm - Make loading of lightgbm-library optional, only loading if file exists. - -# v2024.7.28 -- sqlmath - Update sql-function LGBM_PREDICTFORTABLE() to allow multiclass. -- lgbm - Upgrade to lightgbm-v4.5.0. -- sqlite - Upgrade to sqlite-v3.46.0. -- sqlmath - Add sql-aggregate-function COLUMNTYPE() and allow exporting sql-table as sql-statement. -- sqlmath - Add sql-functions IDATEFROMTEXT(), IDATETOTEXT(). -- python - Upgrade to ruff-v0.5.0. - -# v2024.6.25 -- sqlmath - Update sql-function LGBM_PREDICTFORTABLE() to allow multiclass. -- jslint - Remove unnecessary shell-function shCurlExe(). -- lgbm - Upgrade to lightgbm-v4.4.0. -- jslint - Upgrade to jslint-v2024.6.1-beta. -- lgbm - Include lib_lightgbm.xxx in npm/python package. -- lgbm - Add sqlite-extension for lightgbm. -- lgbm - Add sql-functions: - lgbm_datasetcreatefromfile() - lgbm_datasetcreatefrommat() - lgbm_datasetcreatefromtable() - lgbm_datasetdumptext() - lgbm_datasetfree() - lgbm_datasetgetnumdata() - lgbm_datasetgetnumfeature() - lgbm_datasetsavebinary() - lgbm_dlopen() - lgbm_predictforfile() - lgbm_predictfortable() - lgbm_trainfromdataset() - lgbm_trainfromtable() -- sqlmath - Remove little-used sql-function fill_forward(). -- zlib - Update to zlib v1.3.1. -- sqlmath - Add function dbExecAndReturnLastValue(). -- sqlmath - Replace functions: - dbExecAndReturnFirstRow() with dbExecAndReturnLastRow() - dbExecAndReturnFirstTable() with dbExecAndReturnLastTable() - -# v2024.5.26 -- webdemo - Replace market-indices .spx/.ndx/.dji with futures .es/.nq/.ym. -- sqlite - Decouple c-function str99JsonAppendText() from builtin-function jsonAppendString(). -- sqlite - Update to sqlite v3.44.2. -- sqlmath - Remove little-used sqlean-regexp-extension and file sqlmath_external_pcre2.c. -- sqlmath - bugfix - Fix off-by-2 root-mean-square-deviation calculation of parameter lee in sql-function win_sinefit2(). -- sqlmath - bugfix - Fix null-case handling-behavior for function dbExecAndReturnLastBlobAsync(). -- sqlmath - Add functions dbExecAndReturnFirstRow(), dbExecAndReturnFirstTable(), listOrEmptyList(). -- jslint - Update jslint to v2024.4.1-beta. -- ci - bugfix - Fix package.json config-regression breaking ci. - -# v2024.3.25 -- jslint-ci - Add shell-functions shGitPullrequestCleanup(), shGitPullrequest() to automatically cleanup or create-and-push github-pull-commit to origin/alpha. -- ci - Fix tmpdir in shell-function shBrowserScreenshot(). -- webdemo - Update tables tradebot_intraday_xxx. -- migration - Rename prm ydate to xdate, ytime to xtime. -- sqlmath - Move function dbTableImportAsync from file sqlmath_browser.mjs to sqlmath.mjs. -- ci - Update github-ci for actions/cache, actions/setup-python from nodejs v16 to nodejs v20. -- ci - Update shell-function shRollupFetch() to fix blank date-committed. - -# v2024.1.21 -- sqlmath - Add function assertErrorThrownAsync(). - -# v2023.12.20 -- chart - Improve ergonomics with import tsv. -- betadog - bugfix - Remove over-complicated prm ttt from table y_historical. - -# v2023.11.22 -- sqlmath - Add sql-functions normalizewithsquared(), squaredwithsign(). - -# v2023.10.25 -- jslint - Update jslint to v2023.10.24. -- sqlmath - Split monolithic-file sqlite_rollup.c into separate files: - - sqlmath_external_pcre2.c - - sqlmath_external_sqlite.c - - sqlmath_external_zlib.c -- python - Remove setuptools dependency and implement standalone bdist_wheel() api. -- sqlean - Add sqlean-extension regexp with regexp-replacement and pcre2 - increases wasm size to 1.1mb. -- zlib - Update to zlib v1.3. -- sqlmath - Fix SIGSEGV error when binding external-buffer during db_exec. -- sqlite - Update to sqlite v3.42.0. -- python - Add python-functions db_file_load(), db_file_save(). -- sqlmath - Revamp how js-arraybuffers are passed to c-api without copying. -- python - Revamp python-c-extension to support cp312. -- python - Revamp python-c-extension to use nodejs-like-api dbClose, dbExec, dbFileLoadOrSave, dbNoop, dbOpen. -- sqlmath - Remove unused sql-function-prm in sinefit_extra(), stp. -- sqlmath - Add sql-function-prm in sinefit_extra(), predict_cnr, predict_cos, predict_sin. - -# v2023.9.25 -- sqlmath - Add sql-functions coinflip_extract(), normalizewithsqrt(), win_coinflip2(). - -# v2023.9.19 -- ci - Update ci-workflow publish to auto-publish pypi-package. -- ci - Add ci-workflow publish_pypi_test. -- python - Remove setuptools dependency. - part1 -- python - Update ci to build python-manylinux wheel. -- sqlmath - Add sql-function sqrtwithsign(), and remove redundant sql-function sign(). -- sqlmath - Update function win_quantile2() to accept different quantile-arg for each icol. -- sqlmath - Update function win_sinefit() to allow outputting gaussian-normalized y-value gyy. - -# v2023.8.20 -- jslint - Update jslint to v2023.8.20. -- chart - Add technical-chart tradebot_technical_sinefit. -- sqlmath - bugfix - Fix buffer-overflow in c-function sql3_win_sinefit2_step(). -- sqlmath - Rename c-struct Vector99 to Doublewin. -- sqlmath - Add sql-function fmod(). -- sqlmath - Rename sql-functions xxx_cosfit_xxx() to xxx_sinefit_xxx(). -- sqlmath - Revamp sql-function win_cosfit2() to 1) find frequency cww from dft, and then 2) find phase cpp from linear equation y=b*cos(w*t)+c*sin(w*t). -- sqlmath - Update sql-function win_cosfit2() to use sine() instead of cosine() for better phase-continuity of initial guesses of phase at cpp=0. -- sqlmath - Update sql-agg-function stdev() to aggregate over running-window. -- sqlmath - Update sql-function win_cosfit2_refitlast() to be able to update cosine-fit as well. -- sqlmath - Merge sql-functions win_cosfit2_predict(), win_cosfit2_extract() into sql-function cosfit_extract(). -- sqlmath - Add c-helper-functions doubleAbs(), doubleMax(), doubleMin(). -- sqlmath - merge c-struct WinCosfitInternal, WinCosfitResult into c-struct WinCosfit. -- sqlmath - Update sql-functions win_cosfit2(), win_emax(), win_quantilex() to move variable-length arguments to last position. -- sqlmath - Update sql-function win_cosfit2() with additional argument modeNoCsf. -- sqlmath - Merge sql-functions win_slr2(), win_slrcos2() into sql-function win_cosfit2(). -- sqlmath - Streamline sql-function win_slr2_step() to only update last datapoint. -- sqlmath - Update sql-function win_quantile2(), win_slr2() to input/output doublearray instead of json for better performance. -- sqlmath - Add test-file test_data_cosfit.csv. -- sqlmath - Add sql-function win_slrcos2(). -- sqlmath - Update sql-function win_slr2() to input/output doublearray instead of json for better performance. -- sqlmath - Rename sql-functions jsonfromdoublearray() to doublearray_jsonto(), jsontodoublearray() to doublearray_jsonfrom(). -- sqlmath - Remove unused sql-functions: - btobase64(), btotext() - jenks_blob(), jenks_concat(), jenks_json() - vec_concat() -- betadog - Migrate sql-functions matrix2d_concat() from sqlmath to betadog. -- sqlmath - Add sql-function win_slr2_step() to incrementally step from last slr-state. - -# v2023.7.21 -- sqlmath - Update sql-function win_slr2() to additionally return predicted y-value and rmse. -- betadog - Migrate market-indicator from spy to spx. - -# v2023.6.26 -- sqlmath - fix broken interpolation in sql-function quantile(). -- sqlmath - Add sql-function win_quantile1(), win_quantile2(). -- sqlmath - Update sql-function win_slr2() to be vectorized. -- sqlmath - Add sql-function win_ema2(). -- betadog - Add feature xema_xxx into ml model. -- sqlmath - Add sql-function win_slr(). -- sqlmath - Add sql-function vec_win_slr_updatelast(). -- sqlmath - Add sql-function vec_win_slr(). -- sqlmath - Revamp struct Vector99. -- sqlmath - Add sql-function vec_concat(). -- sqlmath - Add sql-function castrealornull(). - -# v2023.5.25 -- ci - auto-update python version -- jslint - Add grammar for "export async function ...". -- sqlite - Remove hacky sqlite-extension-function. -- python - Migrate python-driver from pysqlite3 to cpython. -- jslint - Add grammar for regexp-named-capture-group and regexp-named-backreference. -- betadog - Reconcile differences with betadog. -- python - Successfully published first pypi package version sqlmath@v0.0.5. -- ci - Rename shell-function shRawLibFetch() to shRollupFetch(). -- sqlmath - migrate build-ext from python to nodejs with parallel compile -- sqlmath - add python c-extension -- sqlmath - streamline macros SQLITE3_C2, SQLITE_H2, SQLITE3EXT_H2, SQLMATH_C2 in prepartion for python-c-extension -- sqlmath - add file zlib_rollup.c so sqlmath can be compiled without external zlib-dependency -- sqlmath - add sql-function avg_ema() -- sqlmath - add sql-function fill_forward() - -# v2023.4.22 -- sqlmath - rename sql-function percentile() to quantile() -- ci - add shell-function shCiPreCustom2() to setup python in custom-ci -- ci - remove mandatory python ci -- sqlmath - update sql-function percentile() with off-by-one bugfix -- sqlmath - remove file sqlmath_fann.c -- sqlmath - add file sqlmath_fann.c - -# v2023.3.21 -- sqlmath - move c-function str99JsonAppendText() from file sqlmath_base.c to sqlite3.c -- sqlmath - merge file sqlite3_ext.c into sqlite3.c -- sqlmath - streamline str99 c-code -- sqlmath - add sql-functions jenks_concat(kk, col1, col2, ...), jenks_json(kk, jsonArray) -- chart - bugfix - fix special-character in col-name being sql-injected - -# v2023.2.26 -- chart - bugfix - use spy for reference previous-ydate instead of 1a_mybot -- sqlmath - add functions dbExecAndReturnLastJsonAsync(), dbExecAndReturnLastTextAsync() -- sqlmath - add sql-functions jfromfloat64array(), jtofloat64array() -- sqlite - update to sqlite v3.39.4 -- ci - replace shell-function shGithubPushBackupAndSquash() with simplified shGitCommitPushOrSquash() -- ci - in windows-ci-env, alias node=node.exe instead of using winpty for pipes - -# v2023.1.29 -- ci - add auto-logo-creation in private repo -- ci - auto-create asset_image_logo_512.png from asset_image_logo_512.html -- jslint-ci - revamp auto-updating and add shell-function shGithubCheckoutRemote -- chart - remove unused external libraries chart.js, moment.js -- demo - bugfix - fix 1-week-chart ignoring friday before monday-holiday - -# v2022.12.31 -- chart - bugfix - fix intraday-weekly-chart truncating first datapoint -- demo - use intraday data for weekly chart -- demo - allow stock ivv to be used as reference if spy is unavailable -- demo - rename main-account from 01_mybot to 1a_mybot - -# v2022.11.20 -- ci - auto-update version-number in main mjs-module -- editor - update codemirror-editor to v5.65.10 -- doc - add api-doc -- doc - document quickstart-build, quickstart-website -- sqlmath - bugfix - fix function dbFileImportOrExport() throwing incorrect error -- chart - improve ergonomics to attach database and import csv/json -- demo - add hotkeys ctrl-o to open database, ctrl-s to save database -- demo - streamline charts sector, subsector, stock into single logic -- sqlmath - remove unused sqlite-extensions carray, csv from file sqlite3_ext.c -- chart - add chart-options xstep, ystep -- ci - update to nodejs v18 -- chart - redesign tooltip -- chart - add xvalueConvert option juliandayToDate -- chart - add dbtable-crud-operations dbtableImportCsv, dbtableImportJson -- chart - add intraday, 1-min chart - -# v2022.10.20 -- demo - add chart tradebot_historical_backtrack -- chart - allow hiding series by clicking it in plot-area -- coverage - add test-coverage for function sqlmathWebworkerInit() -- chart - use different color for category - short -- sqlmath - re-enable sql-function squared() - -# v2022.9.20 -- sqlmath - bugfix - handle null seriesList, xlabelList, xdata, ydata -- sqlmath - revamp str99 with sqlite3_str -- jslint - update jslint v2022.9.9 -- sqlite - revert back to v3.38.5 due to performance-regression -- sqlmath - add sql-function tofloat64array() -- sqlite - update to sqlite v3.39.2 - -# v2022.8.20 -- chart-revamp - switch from chart.js to using svg-elements for plots --- -- chart - add optional easeout ui-animation, in addition to linear -- chart - fix render-bug when zooming with no data -- chart - bugfix - fix xrangeMin being NaN -- chart - animate crosshair, similar to tooltip -- chart - remove gridband code -- chart - remove all classes, and instances of this -- chart - bugfix - fix x-axis having no padding -- chart - bugfix - fix chart failing to re-scale after hiding/showing series -- chart - fix tooltip-indent-bug -- chart - revamp tooltip-popup in barchart - part1 -- chart - replace Tick class with static-functions uichartAxistickCreate and uichartAxistickRender -- chart - bugfix - fix tick-cleanup -- chart - fix seriestracker z-index in reverse-order of series-render -- chart - bugfix - fix annoying clip-path drifting down-right during startup-animation -- chart - remove jquery-dependency -- chart - fix again, zoomout cropping too much of one side, in line-charts --- -- remove unused files - assets.bootstrap-v3.4.1.datatables-v1.10.21.chartjs-v2.9.4.codemirror-5.58.3.rollup.css - assets.bootstrap-v3.4.1.datatables-v1.10.21.chartjs-v2.9.4.codemirror-5.58.3.rollup.js - spa.sqlchart.html - spa.sqlchart.js -- add dbrow-crud-operations dbrowDelete, dbrowInsert, dbrowUpdate -- chart - add charting functionality using chart.js -- add dbtable-crud-operations dbtableRename, dbcolumnAdd, dbcolumnRename, dbcolumnDrop -- demo - add web-demo in README.md -- webapp - add ui-loading when running onDbAction(), onDbExec() -- demo - add github-fork-banner - -# v2022.6.30 -- add dbtable-crud-operations dbquerySaveCsv, dbquerySaveJson, dbtableDrop, dbtableSaveCsv, dbtableSaveJson -- add contextmenu and dbtable-crud-operation dbtableDrop -- add database-crud-operations -- merge rendering of sql-queries and sql-tables into one -- optimization - defer rendering data in dttable until you scroll into it in viewport -- add modal to display sql-query errors -- bugfix - fix broken sorting -- merge datatables css into file index.html -- merge datatables code into file sqlmath_browser.mjs and remove jquery-dependency -- bugfix - fix ci-function shCiBuildWasm() not updating output sqlmath_wasm.wasm -- wasm - rewrite file sqlmath_wrapper_wasm.js to pass jslint -- datatables - rewrite datatables-function _fnDraw() for faster rendering -- sqlmath - milestone - functional index.html -- add files asset_sqlmath_external_rollup.css, asset_sqlmath_external_rollup.js -- bugfix - fix memory-leak by replacing all free()/malloc() with sqlite3_free()/sqlite3_malloc() -- wasm - update functions dbFileExportAsync(), dbFileImportAsync() to be able to export/import raw-db-arraybuffer -- remove obsolete csv-json-import functions like dbTableInsert(), superseded by json_each() -- wasm - update function dbOpen() to be able to import raw dbData arraybuffer -- merge function dbExecWithRetryAsync() into dbExecAsync() -- rename function dbMemoryLoadOrSave to dbFileImportOrExport() -- add file csslint.js -- wasm - replace sqljs-api with sqlmath-api in wasm - part1 -- remove jslint-dependency in sqlmath-core to prepare sqlmath for browser/wasm env -- emscripten updated to v3.1.3, allowing es6 syntax in sqlmath_wrapper_wasm.js -- jenks - streamline jenksCreate() return object to simple double-array instead of struct -- jenks - inline jenks-function jenksCalcRange() from recursion to iteration -- jenks - optimize jenks-function jenksCalcRange() from recursion to iteration -- jenks - replace class-based api with static-function -- jsbaton - migrate function dbExec() to new unified jsbaton -- error - add sqlite errcode SQLITE_ERROR_ZSQL_NULL -- jsbaton - migrate function dbTableInsert() to new unified jsbaton -- jsbaton - migrate functions dbClose(), dbMemoryLoadOrSave(), dbNoop(), dbOpen() to new, streamlined jsbaton2 -- add js-function jsbatonPushValue() to dynamically push value to jsbaton -- napi - decouple c-functions dbClose(), dbExec(), dbMemoryLoadOrSave(), dbNoop(), dbOpen(), dbTableInsert() from napi -- perf - reduce unnecessary copying, replacing SQLITE_TRANSIENT with SQLITE_STATIC -- sqlmath - wrap c-functions jsonInit(), jsonInitNoContext() with jsonInit2() that auto jsonGrow/malloc str99->zBuf -- sqlmath - streamline function cCall() to cCallAsync(), which only returns a promise -- wasm - migrate from sqljs-api to sqlmath-api in wasm - part1 -- gc - replace node-specific-gc __dbFinalizerCreate() with more-webassembly-friendly FinalizationRegistry() -- sqlmath - add sql-extension copyblob(), matrix2d_concat() -- sqlmath - re-include carray-extension - -# v2022.5.20 -- sqlite - update to sqlite v3.38.5 -- sqlmath - add sql-functions castrealorzero(), casttextorempty() -- sqlmath - migrate test to jsTestXxx -- jslint - update jslint v2022.05.13 - -# v2022.4.28 -- jslint - update jslint v2022.4.28 -- sqlite - update to sqlite v3.38.2 -- sqlmath - bugfix - fix crash from sql-fnc kthpercentile(), when passed with empty-list - -# v2022.3.20 -- sqlmath - add builtin sql-function jenks() which is a fast 1d, ml classifier -- sqlmath - merge/streamline buildstep sqlmath_custom.c into sqlmath_base.c -- sqlmath - remove unused responseType = lastMatrixDouble -- sqlmath - export c-function dbExec to wasm -- sqlmath - replace Str99 with sqlite-builtin JsonString -- update to sqlite v3.38.1 -- ci - add fileCount check -- sqlmath - re-enable building shell-executable -- sqlmath - use sqlite-builtin jsonstring instead of str99 for extension kthpercentile -- sqlmath - remove zlib.h and use handcoded headers instead -- split build-step sqlite3_c to sqlite3_c, sqlite3_ext_c, to deduplicate definition SQLITE3_C2 -- add shell-function shCiBuildWasm() to build wasm binaries with online demo spa.sqlchart.html -- update to sqlite v3.38.0 -- create github-branch-artifact to upload shared binaries - -# v2022.3.5 -- update jslint v2022.2.20 -- add sqlite-function RANDOM1() generating random-float between 0 <= xx < 1 -- remove file sqlmath_old.js -- add sql-function kthpercentile(val, percentile) -- update sql-functions compress(), uncompress() with null check/guard - -# v2021.11.20 -- npm publish v2021.10.20 -- update to jslint v2021.10.20 -- refactor - replace nodejs-buffer-api-calls with DataView / TextDecoder / TextEncoder - -# v2021.10.20 -- revamp - update to jslint_ci.sh from 2020.10.2 - -# v2021.9.9 -- add sql-function slr_ohlcv() with tests -- update file jslint_ci.sh with chorish shell-functions -- update function dbOpenAsync with ability to create thread-pool with multiple connections - -# v2021.9.8 -- merge files in sqlite-autoconf-3360000 into sqlite3.c, sqlite3_ext.c, sqlite3_shell.c - -# v2021.9.4 -- add functions dbGetLastBlob, dbTableInsertAsync -- merge file sqlmath_napi.c into sqlmath.c -- merge file test.js into sqlmath.mjs -- migrate repo to https://github.com/sqlmath/sqlmath -- migrate to esm and rename sqlmath.js to sqlmath.mjs - -# v2021.8.26 -- merge files sqlmath.h and sqlmath_napi.c into sqlmath.c -- add async functions dbOpen, dbExec, dbClose -- inline function sqlite3_extension_functions_init without auto_extension -- disable warnings for all targets except sqlmath.c and sqlmath_napi.c -- fix build for darwin and win32. - -# v2021.8.24 -- add addon functions dbOpen(), dbClose(), dbExec(). but ci works only in linux. - -# v2021.8.16 -- add extension-function b64decode() -- add c-function sqlite3_exec_get_tables(). -- add target_cli. -- binaries - upload darwin/linux/win32 binaries to gh-pages. -- begin compiling sqlmath.c. -- jslint file sqlmath.js. -- merge file test-old-prepare.db into test-old.js. - -# v2021.8.6 -- add coverage testing. -- add extension-function support. -- fork repo as sqlmath. - -## 5.0.2 -- build: rebuild binaries before publishing [#1426](https://github.com/mapbox/node-sqlite3/pull/1426) - -## 5.0.1 -- dep: node-addon-api to ^3.0.0 [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) -- bug: bad comparison of c string [#1347](https://github.com/mapbox/node-sqlite3/pull/1347) -- build: Install files to be deployed [#1352](https://github.com/mapbox/node-sqlite3/pull/1352) -- sqlite3: upgrade to 3.32.3 [#1351](https://github.com/mapbox/node-sqlite3/pull/1351) -- bug: worker threads crash [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) -- bug: segfaults [#1368](https://github.com/mapbox/node-sqlite3/pull/1368) -- typo: broken link to MapBox site [#1369](https://github.com/mapbox/node-sqlite3/pull/1369) - -## 5.0.0 -- prebuilt: Node 14 support, dropped support for all version of Node < 10 [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) -- prebuilt: add electron 7.2 [#1324](https://github.com/mapbox/node-sqlite3/pull/1324) -- napi: refactor codebase to use N-API instead of NAN (+ various improvements) [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) -- trace: don't require throw to add trace info for verbose [#1317](https://github.com/mapbox/node-sqlite3/pull/1317) -- ci: remove permission setting [#1319](https://github.com/mapbox/node-sqlite3/pull/1319) - -## 4.2.0 -- electron: Electron v8, v8.1.x & v8.2.x [#1294](https://github.com/mapbox/node-sqlite3/pull/1294) [#1308](https://github.com/mapbox/node-sqlite3/pull/1308) -- sqlite3: update to 3.31.1 (3310100) [#1289](https://github.com/mapbox/node-sqlite3/pull/1289) -- webpack: split sqlite3-binding.js out so that it could be override by webpack [#1268](https://github.com/mapbox/node-sqlite3/pull/1268) -- sqlite3: enable 'SQLITE_ENABLE_DBSTAT_VTAB=1' [#1281](https://github.com/mapbox/node-sqlite3/pull/1281) -- deps: remove request [#1287](https://github.com/mapbox/node-sqlite3/pull/1287) -- deps: alternative update of node-gyp for electron (v1 - v4), windows [#1283](https://github.com/mapbox/node-sqlite3/pull/1283) -- electron: fix dist url [#1282](https://github.com/mapbox/node-sqlite3/pull/1282) -- docs: Added json1 support note [#1303](https://github.com/mapbox/node-sqlite3/pull/1303) - -## 4.1.1 -- Electron v6.1 and v7 support [#1237](https://github.com/mapbox/node-sqlite3/pull/1237) -- Electron v7.1 support [#1254](https://github.com/mapbox/node-sqlite3/pull/1254) -- SQLite3 update to 3.30.1 [#1238](https://github.com/mapbox/node-sqlite3/pull/1238) -- Overwrite 'msbuild_toolset' only if 'toolset' is defined [#1242](https://github.com/mapbox/node-sqlite3/pull/1242) -- Upgrade CI to node-gyp 6.x for Windows Electron v5 & v6 builds [#1245](https://github.com/mapbox/node-sqlite3/pull/1245) -- Node v13 support [#1247](https://github.com/mapbox/node-sqlite3/pull/1247) -- Use minimum supported node version for Electron 7 [#1255](https://github.com/mapbox/node-sqlite3/pull/1255) - -## 4.1.0 - -- Electron v6 support [#1195](https://github.com/mapbox/node-sqlite3/pull/1195) -- Electron v4.1 and v4.2 support [#1180](https://github.com/mapbox/node-sqlite3/pull/1180) -- Custom file header with `--sqlite_magic` [#1144](https://github.com/mapbox/node-sqlite3/pull/1144) -- https everywhere [#1177](https://github.com/mapbox/node-sqlite3/pull/1177) - -## 4.0.9 -- Use trusty as the base for prebuilts [#1167](https://github.com/mapbox/node-sqlite3/pull/1167) - -## 4.0.8 -- Rerelease of 4.0.7 but removed excess .vscode files [0df90c7](https://github.com/mapbox/node-sqlite3/commit/0df90c7811331169ad5f8fbad396422e72757af3) - -## 4.0.7 - -- Node v12 support -- Electron v5 support -- Fix backup API tests -- HAVE_USLEEP=1 for all platforms -- docker suport - -## 4.0.6 -- Release of 4.0.5 (again due CI) - -## 4.0.5 -- **SECURITY:** Upgrade SQLite to 3.26.0 [#1088](https://github.com/mapbox/node-sqlite3/pull/1088) -- add constants for file open (shared databases) [#1078](https://github.com/mapbox/node-sqlite3/pull/1078) -- Allow specifying the python to use [#1089](https://github.com/mapbox/node-sqlite3/pull/1089) - -## 4.0.4 -- Add NodeJS 11 support [#1072](https://github.com/mapbox/node-sqlite3/pull/1072) -- Add electron osx 3.0.0 support [#1071](https://github.com/mapbox/node-sqlite3/pull/1071) - -## 4.0.3 - -- Increase electron/osx binary coverage [#1041](https://github.com/mapbox/node-sqlite3/pull/1041) (@kewde) - -## 4.0.2 - -- Fixed HTTP proxy support by using `request` over `needle` in node-pre-gyp - -## 4.0.1 - -- Node v10 support -- Upgrade to node-pre-gyp@0.10.1 -- Upgrade to nan@2.10.0 -- Upgrade to sqlite v3.24.0 -- Stopped bundling node-pre-gyp -- Upgrade to mocha@5 -- Now building electron binaries (@kewde) -- Add OPEN_FULLMUTEX constant - -## 4.0.0 - - - Drop support for Node v0.10 and v.12 - - Upgrade to node-pre-gyp@0.9.0 - - Upgrade to nan@2.9.2 - -## 3.1.13 - -- Attempt to fix regression of #866 - -## 3.1.12 - -- Fixed to ensure the binaries do not rely on `GLIBC_2.14` and only `GLIBC_2.2.5`. This regressed in v3.1.11. - -## 3.1.11 - -- Fixed building from source on alpine linux - -## 3.1.10 - -- Removed `npm ls` from `prepublish` hook per mapbox/node-pre-gyp#291 -- Upgraded node-pre-gyp to v0.6.37 -- Removed accidentally committed large file - -## 3.1.9 - -- Added support for node v8 and upgraded `nan`, `node-pre-gyp` deps. - -## 3.1.8 - -- Added support for node v7 (pre-compiled binaries available) - -## 3.1.7 - -- Upgrade sqlite to 3.15, enable FTS4, FTS5 (@wmertens) -- Upgrade to node-pre-gyp@0.6.31 and nan@2.4.0 - -## 3.1.6 - -- Starts bundling node-pre-gyp again to avoid #720 - -## 3.1.5 - -- [Added support for sqlite3_interrupt](https://github.com/mapbox/node-sqlite3/pull/518): this makes - it possible to interrupt a long-running query. -- [Fixes uv_ref race](https://github.com/mapbox/node-sqlite3/pull/705). - -## 3.1.4 - - - Added support for node v6 - -## 3.1.3 - - - Upgrade to node-pre-gyp@0.6.26 with better support for Electron - -## 3.1.2 - - - Only providing binaries for node v0.10x, v0.12.x, v4, and v5 - - Upgrade to nan@2.2.x - - Upgrade to node-pre-gyp@0.6.24 - - -## 3.1.1 - - - Support for node 5.x - - Upgraded SQLite to 3.9.1: https://www.sqlite.org/releaselog/3_9_1.html - - Enabled json1 extension by default - -## 3.1.0 - - - Support for node 3.x and 4.x - - Stopped producing binaries for node-webkit and 32 bit linux - -## 3.0.11 - - - Support for io.js 3.x (upgrade to Nan 2.x) @kkoopa - -## 3.0.10 - - - Upgraded SQLite to 3.8.11.1: https://www.sqlite.org/releaselog/3_8_11_1.html - - Fixed binary compatibility regression with old centos/rhel glibc GLIBC_2.14 (re-introduced alpine linux (musl) build regression) - - Now providing binaries against Visual Studio 2015 (pass --toolset=v140) and use binaries from https://github.com/mapbox/node-cpp11 - -## 3.0.9 - - - Fixed build regression against alpine linux (musl) - - Upgraded node-pre-gyp@0.6.8 - -## 3.0.8 - - - Fixed build regression against FreeBSD - - Upgraded node-pre-gyp@0.6.7 - -## 3.0.7 - - - Fixed build regression against ARM and i386 linux - - Upgraded node-pre-gyp@0.6.6 - - Added support for io.js 2.0.0 - -## 3.0.6 - - - Upgraded node-pre-gyp@0.6.5 - - Upgraded nan@1.8.4 - - Fixed binaries to work on older linux systems (circa GLIBC_2.2.5 like centos 6) @bnoordhuis - - Updated internal libsqlite3 from 3.8.7.1 -> 3.8.9 (https://www.sqlite.org/news.html) - -## 3.0.5 - - - IO.js and Node v0.12.x support. - - Node-webkit v0.11.x support regressed in this release, sorry (https://github.com/mapbox/node-sqlite3/issues/404). - -## 3.0.4 - - - Upgraded node-pre-gyp@0.6.1 - -## 3.0.3 - - - Upgraded to node-pre-gyp@0.6.0 which should fix crashes against node v0.11.14 - - Now providing binaries against Visual Studio 2014 (pass --toolset=v140) and use binaries from https://github.com/mapbox/node-cpp11 - -## 3.0.2 - - - Republish for possibly busted npm package. - -## 3.0.1 - - - Use ~ in node-pre-gyp semver for more flexible dep management. - -## 3.0.0 - -Released September 20nd, 2014 - - - Backwards-incompatible change: node versions 0.8.x are no longer supported. - - Updated to node-pre-gyp@0.5.27 - - Updated NAN to 1.3.0 - - Updated internal libsqlite3 to v3.8.6 - -## 2.2.7 - -Released August 6th, 2014 - - - Removed usage of `npm ls` with `prepublish` target (which breaks node v0.8.x) - -## 2.2.6 - -Released August 6th, 2014 - - - Fix bundled version of node-pre-gyp - -## 2.2.5 - -Released August 5th, 2014 - - - Fix leak in complete() callback of Database.each() (#307) - - Started using `engineStrict` and improved `engines` declaration to make clear only >= 0.11.13 is supported for the 0.11.x series. - -## 2.2.4 - -Released July 14th, 2014 - - - Now supporting node v0.11.x (specifically >=0.11.13) - - Fix db opening error with absolute path on windows - - Updated to node-pre-gyp@0.5.18 - - updated internal libsqlite3 from 3.8.4.3 -> 3.8.5 (https://www.sqlite.org/news.html) - -## 2.2.3 - - - Fixed regression in v2.2.2 for installing from binaries on windows. - -## 2.2.2 - - - Fixed packaging problem whereby a `config.gypi` was unintentially packaged and could cause breakages for OS X builds. - -## 2.2.1 - - - Now shipping with 64bit FreeBSD binaries against both node v0.10.x and node v0.8.x. - - Fixed solaris/smartos source compile by passing `-std=c99` when building internally bundled libsqlite3 (#201) - - Reduced size of npm package by ignoring tests and examples. - - Various fixes and improvements for building against node-webkit - - Upgraded to node-pre-gyp@0.5.x from node-pre-gyp@0.2.5 - - Improved ability to build from source against `sqlcipher` by passing custom library name: `--sqlite_libname=sqlcipher` - - No changes to C++ Core / Existing binaries are exactly the same - -## 2.2.0 - -Released Jan 13th, 2014 - - - updated internal libsqlite3 from 3.7.17 -> 3.8.2 (https://www.sqlite.org/news.html) which includes the next-generation query planner https://www.sqlite.org/queryplanner-ng.html - - improved binary deploy system using https://github.com/springmeyer/node-pre-gyp - - binary install now supports http proxies - - source compile now supports freebsd - - fixed support for node-webkit - -## 2.1.19 - -Released October 31st, 2013 - - - Started respecting `process.env.npm_config_tmp` as location to download binaries - - Removed uneeded `progress` dependency - -## 2.1.18 - -Released October 22nd, 2013 - - - `node-sqlite3` moved to mapbox github group - - Fixed reporting of node-gyp errors - - Fixed support for node v0.6.x - -## 2.1.17 - - Minor fixes to binary deployment - -## 2.1.16 - - Support for binary deployment - -## 2.1.15 - -Released August 7th, 2013 - - - Minor readme additions and code optimizations diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1d056470ba..0000000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2021 Kai Zhu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a0dc1e68b6..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,44 +0,0 @@ -include .ci.sh -include .gitconfig -include .github/workflows/ci.yml -include .github/workflows/publish.yml -include .github/workflows/publish_pypi_test.yml -include .gitignore -include .npmignore -include CHANGELOG.md -include LICENSE -include MANIFEST.in -include PKG-INFO -include README.md -include asset_image_folder_open_solid.svg -include asset_image_github_brands.svg -include asset_image_logo_256.html -include asset_image_logo_256.png -include asset_image_logo_256.svg -include asset_sqlmath_external_rollup.js -include cpplint.py -include csslint.js -include indent.exe -include index.html -include jslint.mjs -include jslint_ci.sh -include libiconv2.dll -include libintl3.dll -include package.json -include pyproject.toml -include setup.py -include sqlmath.mjs -include sqlmath/__init__.py -include sqlmath_base.c -include sqlmath_base.h -include sqlmath_browser.mjs -include sqlmath_external_sqlite.c -include sqlmath_wrapper_wasm.js -include test.mjs -include test.py -include test_data_sinefit.csv -include test_lgbm_binary.test -include test_lgbm_binary.train -include test_lgbm_numpy.py -include test_lgbm_preb.txt -include zlib.v1.3.1.vcpkg.x64-windows-static.lib diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 90fba4d28a..0000000000 --- a/PKG-INFO +++ /dev/null @@ -1,696 +0,0 @@ -Metadata-Version: 2.1 -Name: sqlmath -Version: 2026.7.30 -Summary: sqlite for data-science -Author: Kai Zhu -Requires-Python: >=3.10 -License: MIT -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: MacOS -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: POSIX :: Linux -Classifier: Programming Language :: C -Classifier: Programming Language :: JavaScript -Classifier: Programming Language :: Python -Classifier: Topic :: Database -Classifier: Topic :: Scientific/Engineering -Classifier: Topic :: Software Development -Project-URL: Homepage, https://github.com/sqlmath/sqlmath -Project-URL: Changelog, https://github.com/sqlmath/sqlmath/blob/master/CHANGELOG.md -License-File: LICENSE -Description-Content-Type: text/markdown - -# sqlmath - -**SQLite with data-science superpowers.** Run statistics, machine learning, and analytics directly in SQL queries — from Python, JavaScript, or the browser. - -[![PyPI](https://img.shields.io/badge/PyPI-Install_Package-3775A9?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/sqlmath/) -[![npm](https://img.shields.io/badge/npm-Install_Package-CB3837?style=for-the-badge&logo=npm&logoColor=white)](https://www.npmjs.com/package/sqlmath) -[![Demo](https://img.shields.io/badge/Demo-Try_Live-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white)](https://sqlmath.github.io/sqlmath/index.html) -[![API Docs](https://img.shields.io/badge/Docs-API_Reference-blue?style=for-the-badge&logo=readthedocs&logoColor=white)](https://sqlmath.github.io/sqlmath/apidoc.html) - -# Status - -| Branch | [master
(v2026.7.30)](https://github.com/sqlmath/sqlmath/tree/master) | [beta
(Web Demo)](https://github.com/sqlmath/sqlmath/tree/beta) | [alpha
(Development)](https://github.com/sqlmath/sqlmath/tree/alpha) | -|--:|:--:|:--:|:--:| -| CI | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Amaster) | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=beta)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Abeta) | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=alpha)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Aalpha) | -| Coverage | [![coverage](https://sqlmath.github.io/sqlmath/branch-master/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-master/.artifact/coverage/index.html) | [![coverage](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/coverage/index.html) | [![coverage](https://sqlmath.github.io/sqlmath/branch-alpha/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-alpha/.artifact/coverage/index.html) | -| Demo | [](https://sqlmath.github.io/sqlmath/branch-master/index.html) | [](https://sqlmath.github.io/sqlmath/branch-beta/index.html) | [](https://sqlmath.github.io/sqlmath/branch-alpha/index.html) | -| Artifacts | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-master/.artifact) | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-beta/.artifact) | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-alpha/.artifact) | - - -

-# Table of Contents - -1. [Web Demo](#web-demo) - -2. [Why sqlmath?](#why-sqlmath) - -3. [Quickstart](#quickstart) - - [Python](#python) - - [JavaScript (Node.js)](#javascript-nodejs) - - [Browser (WebAssembly)](#browser-webassembly) - -4. [Built-in SQL Functions](#built-in-sql-functions) - - [Math](#math) - - [Statistics (Aggregate)](#statistics-aggregate) - - [Statistics (Scalar)](#statistics-scalar) - - [Arrays](#arrays) - - [Date/Time](#datetime) - - [Time Series / Signal Processing](#time-series--signal-processing) - - [Compression](#compression) - - [Type Casting](#type-casting) - - [Cryptography](#cryptography) - -5. [Machine Learning with LightGBM](#machine-learning-with-lightgbm) - - [Training from a Table](#training-from-a-table) - - [Prediction](#prediction) - - [Real-World Example: Credit Card Fraud Detection](#real-world-example-credit-card-fraud-detection) - - [LightGBM Functions](#lightgbm-functions) - -6. [Use Cases](#use-cases) - - [Financial Data Analysis](#financial-data-analysis) - - [Embedded ML Pipelines](#embedded-ml-pipelines) - - [Data Compression & Hashing](#data-compression--hashing) - -7. [Why sqlmath vs Alternatives?](#why-sqlmath-vs-alternatives) - -8. [Python API Reference](#python-api-reference) - - [db_exec()](#db_exec) - -9. [Node.js API Reference](#nodejs-api-reference) - -10. [Platform Notes](#platform-notes) - -11. [Building from Source](#building-from-source) - - [Prerequisites](#prerequisites) - - [Build](#build) - - [Run Tests](#run-tests) - - [Serve Demo Locally](#serve-demo-locally) - -12. [Package Listing](#package-listing) - -13. [Changelog](#changelog) - -14. [License](#license) - -15. [Devops Instruction](#devops-instruction) - - [python pypi publish](#python-pypi-publish) - - [sqlite upgrade](#sqlite-upgrade) - - -

-# Web Demo -- https://sqlmath.github.io/sqlmath/index.html - -[![screenshot](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_browser__2fsqlmath_2fbranch-beta_2findex.html.png)](https://sqlmath.github.io/sqlmath/index.html) - - -

-# Why sqlmath? - -SQLite is everywhere — it's the most deployed database in the world. But it lacks the statistical and ML functions needed for data-science. sqlmath fixes that. - -**What you get:** -- **20+ built-in functions** for math, statistics, arrays, dates, and compression -- **LightGBM integration** — train and predict ML models directly in SQL -- **Zero dependencies** — single binary, no external libraries needed -- **Multi-platform** — Python, Node.js, browser (WebAssembly) - - -

-# Quickstart - - -

-### Python - -```shell -pip install sqlmath -``` - -```python -from sqlmath import db_open, db_exec, db_close - -# Open an in-memory database -db = db_open(":memory:") - -# Create sample data -db_exec(db=db, sql=""" - CREATE TABLE prices (symbol TEXT, price REAL); - INSERT INTO prices VALUES ('AAPL', 150.0), ('GOOGL', 140.0), ('MSFT', 380.0); -""") - -# Use built-in statistical functions -result = db_exec(db=db, sql=""" - SELECT - AVG(price) AS mean_price, - MEDIAN2(price) AS median_price, - STDEV(price) AS stdev_price - FROM prices -""") -print(result) -# [[{'mean_price': 223.33, 'median_price': 150.0, 'stdev_price': 135.77}]] - -db_close(db) -``` - - -

-### JavaScript (Node.js) - -```shell -npm install sqlmath -``` - -```javascript -import { dbOpenAsync, dbExecAsync, dbCloseAsync } from "sqlmath"; - -const db = await dbOpenAsync({ filename: ":memory:" }); - -await dbExecAsync({ - db, - sql: ` - CREATE TABLE prices (symbol TEXT, price REAL); - INSERT INTO prices VALUES ('AAPL', 150.0), ('GOOGL', 140.0), ('MSFT', 380.0); - ` -}); - -const result = await dbExecAsync({ - db, - sql: "SELECT AVG(price) AS mean_price FROM prices" -}); -console.log(result); - -await dbCloseAsync(db); -``` - - -

-### Browser (WebAssembly) - -Try it live: **[sqlmath.github.io/sqlmath](https://sqlmath.github.io/sqlmath/index.html)** - - -

-# Built-in SQL Functions - - -

-### Math - -| Function | Description | -|----------|-------------| -| `cot(x)` | Cotangent | -| `coth(x)` | Hyperbolic cotangent | -| `fmod(x, y)` | Floating-point modulo | -| `squared(x)` | Square of x | -| `squaredwithsign(x)` | Square preserving sign (negative input → negative output) | -| `sqrtwithsign(x)` | Square root preserving sign | -| `normalizewithsqrt(x)` | Normalize using square root | -| `normalizewithsquared(x)` | Normalize using square | - - -

-### Statistics (Aggregate) - -| Function | Description | -|----------|-------------| -| `MEDIAN2(x)` | Median value (aggregate) | -| `PERCENTILE(x, p)` | Percentile at p (aggregate) | -| `QUANTILE(x, q)` | Quantile at q (aggregate) | -| `STDEV(x)` | Standard deviation (sample, window-capable) | - - -

-### Statistics (Scalar) - -| Function | Description | -|----------|-------------| -| `marginoferror95(p, n)` | 95% margin of error: sqrt(p*(1-p)/n) | -| `random1()` | Random float in [0, 1) | - - -

-### Arrays - -| Function | Description | -|----------|-------------| -| `doublearray_array(...)` | Create double array from values | -| `doublearray_extract(arr, i)` | Extract element at index | -| `doublearray_jsonfrom(json)` | Create array from JSON | -| `doublearray_jsonto(arr)` | Convert array to JSON | - - -

-### Date/Time - -sqlmath extends SQLite's date functions with integer-format conversions (YYYYMMDDHHMMSS). - - -

-### Time Series / Signal Processing - -| Function | Description | -|----------|-------------| -| `WIN_SINEFIT2(...)` | Fit sine wave to data (aggregate window function) | -| `SINEFIT_REFITLAST(...)` | Refit sine wave with new data point | - - -

-### Compression - -| Function | Description | -|----------|-------------| -| `GZIP_COMPRESS(blob)` | Compress BLOB with gzip | -| `GZIP_UNCOMPRESS(blob)` | Decompress gzip BLOB | - -> **Note:** Input must be BLOB. Use `CAST(text AS BLOB)` for text data. - - -

-### Type Casting - -| Function | Description | -|----------|-------------| -| `castrealornull(x)` | Cast to REAL or NULL | -| `castrealorzero(x)` | Cast to REAL or 0.0 | -| `casttextorempty(x)` | Cast to TEXT or '' | -| `roundorzero(x, n)` | Round or return 0 | - - -

-### Cryptography - -| Function | Description | -|----------|-------------| -| `sha256(data)` | SHA-256 hash (returns BLOB) | - -```sql -SELECT HEX(sha256('hello')) AS hash; --- 2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824 -``` - - -

-# Machine Learning with LightGBM - -sqlmath embeds [LightGBM](https://lightgbm.readthedocs.io/) for gradient boosting directly in SQL queries. Train models on your data without leaving SQL — no data shuffle to Python needed. - -> **Python users:** You must call `lgbm_dlopen()` before using any `lgbm_*` functions. See [Platform Notes](#platform-notes) for details. - - -

-### Training from a Table - -`LGBM_TRAINFROMTABLE` is an **aggregate function** — it consumes rows like `SUM()` or `AVG()`, but outputs a trained model BLOB. - -```sql --- Create a table to store the model -CREATE TABLE model_store (model BLOB); - --- Train directly from your data table -INSERT INTO model_store(model) -SELECT - LGBM_TRAINFROMTABLE( - -- Training parameters - ( - 'objective=binary' - || ' learning_rate=0.1' - || ' metric=auc' - || ' num_leaves=31' - || ' verbosity=0' - ), - 50, -- num_iterations - 10, -- eval_step (early stopping check interval) - 'max_bin=15', -- data parameters - NULL, -- reference dataset (NULL for training set) - -- Columns: first column MUST be the label - label, feature1, feature2, feature3, feature4 - ) -FROM training_data; -``` - - -

-### Prediction - -```sql --- Predict on new data using the stored model -SELECT - id, - LGBM_PREDICTFORTABLE( - (SELECT model FROM model_store), - 0, -- predict_type (0 = normal probability) - 0, -- start_iteration - 50, -- num_iterations - '', -- prediction parameters - feature1, feature2, feature3, feature4 - ) AS prediction -FROM test_data; -``` - - -

-### Real-World Example: Credit Card Fraud Detection - -From the [Kaggle notebook](https://www.kaggle.com/code/kaizhu256/sql-is-all-you-need) — training on 284,807 transactions with 0.17% fraud rate: - -```python -from sqlmath import db_open, db_exec, db_table_import - -db = db_open("fraud.sqlite") - -# Import Kaggle credit card dataset -db_table_import(db=db, filename="creditcard.csv", table_name="transactions", mode="csv") - -# Split 80/20 train/test -db_exec(db=db, sql=""" - CREATE TABLE train AS SELECT * FROM transactions WHERE RANDOM() % 5 != 0; - CREATE TABLE test AS SELECT * FROM transactions WHERE RANDOM() % 5 = 0; -""") - -# Train with automatic class imbalance handling -db_exec(db=db, sql=""" - CREATE TABLE model_store (model BLOB); - - INSERT INTO model_store(model) - SELECT - LGBM_TRAINFROMTABLE( - ( - 'objective=binary' - || ' learning_rate=0.05' - || ' metric=auc' - || ' is_unbalance=true' -- Auto-weight minority class - || ' num_leaves=31' - || ' verbosity=0' - ), - 100, -- num_iterations - 10, -- eval_step - 'max_bin=255', -- data_params - NULL, -- reference - -- First column = label, rest = features (V1-V28 + Amount) - Class, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, - V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, - V21, V22, V23, V24, V25, V26, V27, V28, Amount - ) - FROM train; -""") -``` - - -

-### LightGBM Functions - -| Function | Type | Description | -|----------|------|-------------| -| `lgbm_dlopen(path)` | scalar | Load LightGBM library (required in Python) | -| `LGBM_TRAINFROMTABLE(params, n_iter, eval, data_params, ref, label, ...)` | aggregate | Train model from table columns | -| `LGBM_TRAINFROMFILE(params, n_iter, eval, train_file, data_params, test_file)` | scalar | Train from LibSVM file | -| `LGBM_PREDICTFORTABLE(model, type, start, n, params, ...)` | window | Predict per row | -| `LGBM_PREDICTFORFILE(model, type, start, n, params, file, header, out)` | scalar | Predict and save to file | -| `lgbm_extract(result, key)` | scalar | Extract value from result | - -**Complete Example:** - -[![Kaggle](https://img.shields.io/badge/Kaggle-SQL_is_All_You_Need-20BEFF?style=for-the-badge&logo=kaggle&logoColor=white)](https://www.kaggle.com/code/kaizhu256/sql-is-all-you-need) - -Full notebook with fraud detection, intraday trading signals, and model persistence. - -**Kaggle Environment:** -- Linux x64, Python 3.12, Node.js 24 -- Datasets: [Credit Card Fraud](https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud) (284K transactions), SPY intraday OHLCV -- sqlmath installs via `pip install sqlmath==2026.7.30` - - -

-# Use Cases - - -

-### Financial Data Analysis - -```python -from sqlmath import db_open, db_exec, db_close, db_table_import - -db = db_open(":memory:") - -# Import OHLCV data -db_table_import(db=db, table_name="prices", filename="prices.csv", mode="csv") - -# Calculate rolling statistics in SQL -result = db_exec(db=db, sql=""" - SELECT - date, - close, - AVG(close) OVER (ORDER BY date ROWS 20 PRECEDING) AS sma_20, - STDEV(close) OVER (ORDER BY date ROWS 20 PRECEDING) AS volatility, - PERCENTILE(volume, 50) OVER (ORDER BY date ROWS 5 PRECEDING) AS median_volume - FROM prices - ORDER BY date DESC - LIMIT 10 -""") - -db_close(db) -``` - - -

-### Embedded ML Pipelines - -Train and deploy models without data movement: - -```python -# 1. Load data into SQLite -db_table_import(db=db, table_name="features", filename="training_data.csv", mode="csv") - -# 2. Train model in-database -db_exec(db=db, sql=""" - CREATE TABLE models AS - SELECT LGBM_TRAINFROMTABLE(...) AS model FROM features -""") - -# 3. Score new data in-database -db_exec(db=db, sql=""" - SELECT id, LGBM_PREDICTFORTABLE(model, ...) AS score - FROM new_data, models -""") - -# 4. Export results -db_file_save(db=db, filename="scored_data.sqlite") -``` - - -

-### Data Compression & Hashing - -```sql --- Compress large text fields -UPDATE documents SET - content_compressed = GZIP_COMPRESS(CAST(content AS BLOB)); - --- Generate content hashes for deduplication -SELECT HEX(sha256(content)) AS content_hash, COUNT(*) -FROM documents -GROUP BY content_hash -HAVING COUNT(*) > 1; -``` - - -

-# Why sqlmath vs Alternatives? - -| Feature | sqlmath | pandas | DuckDB | sqlite3 | -|---------|---------|--------|--------|---------| -| In-database ML | ✅ LightGBM | ❌ | ❌ | ❌ | -| Zero data shuffle | ✅ | ❌ | ✅ | ✅ | -| Statistical functions | ✅ 20+ | ✅ | ✅ | ❌ | -| Browser support | ✅ WASM | ❌ | ❌ | ❌ | -| Single file deployment | ✅ | ❌ | ✅ | ✅ | -| Memory efficiency | ✅ Streaming | ❌ In-memory | ✅ | ✅ | - -**Choose sqlmath when:** -- You need ML training/inference inside the database -- Your data is already in SQLite -- You want browser-based analytics (WebAssembly) -- You need statistical functions beyond basic SQL - -**Choose pandas when:** -- You need complex data transformations -- Interactive exploration with rich visualization -- Your workflow is Python-centric - -**Choose DuckDB when:** -- You need fast analytical queries on large datasets -- You want pandas DataFrame interop -- OLAP workloads are primary - - -

-# Python API Reference - -```python -from sqlmath import ( - db_open, # Open database - db_close, # Close database - db_exec, # Execute SQL, return results - db_exec_and_return_lastblob, # Execute SQL, return last blob - db_file_load, # Load database from file - db_file_save, # Save database to file - db_table_import, # Import data into table - db_noop, # No-op for testing -) -``` - - -

-### db_exec() - -```python -result = db_exec( - db=db, # Database connection - sql="SELECT ...", # SQL statement(s) - bind_list=[...], # Bind parameters (list or dict) - response_type=None, # None (default) returns list-of-dicts - # "list" | "lastblob" | "arraybuffer" -) -# Default: [[{'col1': val1, 'col2': val2}, ...]] -``` - - -

-# Node.js API Reference -- https://sqlmath.github.io/sqlmath/apidoc.html - -[![screenshot](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_browser__2f.artifact_2fapidoc.html.png)](https://sqlmath.github.io/sqlmath/apidoc.html) - - -

-# Platform Notes - -The JavaScript (Node.js) binding is more mature than Python. Key differences: - -| Feature | Node.js | Python | -|---------|---------|--------| -| Async vs sync api | **async only:** `result = await dbExecAsync({...})` | **sync only:** `result = db_exec(...)` | -| Connection pooling | ✅ `db = await dbOpenAsync({threadCount: 4, ...})` | ❌ `db = db_open(...)` | -| Type hints | N/A | ❌ Not yet | -| Context manager | N/A | ❌ `with db_open()` not supported | - -> **Note:** The Python wrapper is a work in progress. Contributions welcome! - - -

-# Building from Source - - -

-### Prerequisites - -- Node.js 24+ -- Python 3.12+ -- C compiler (gcc, clang, or MSVC) - - -

-### Build - -```shell -#!/bin/sh - -# git clone sqlmath repo -git clone https://github.com/sqlmath/sqlmath --branch=beta --single-branch -cd sqlmath - -# Build native binary -npm run test2 - -# Build WebAssembly (optional) -sh jslint_ci.sh shCiBuildWasm -``` - - -

-### Run Tests - -```shell -# Full test suite (includes full clean-build) -npm run test2 - -# Full test suite (includes partial re-build of modified files) -npm run test - -# Quick test (skip build) -npm run test --fast -``` - - -

-### Serve Demo Locally - -```shell -PORT=8080 sh jslint_ci.sh shHttpFileServer -# Open http://localhost:8080/index.html -``` - - -

-# Package Listing -![screenshot_package_listing.svg](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_package_listing.svg) - - -

-# Changelog -- [Full CHANGELOG.md](CHANGELOG.md) - -![screenshot_changelog.svg](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_changelog.svg) - - -

-# License -- [sqlite](https://github.com/sqlite/sqlite) is under [public domain](https://www.sqlite.org/copyright.html). -- [jslint](https://github.com/jslint-org/jslint) is under [Unlicense License](https://github.com/jslint-org/jslint/blob/master/LICENSE). -- [zlib](https://github.com/madler/zlib) is under [zlib License](https://github.com/madler/zlib/blob/v1.3.1/LICENSE). -- [cpplint.py](cpplint.py) is under [3-Clause BSD License](https://github.com/cpplint/cpplint/blob/2.0.0/LICENSE). -- [indent.exe](indent.exe) is under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.txt). -- Everything else is under MIT License. - - -

-# Devops Instruction - - -

-### python pypi publish -```shell -python -m build -# -twine upload --repository testpypi dist/sqlmath-2026.7.30* -py -m pip install --index-url https://test.pypi.org/simple/ sqlmath==2026.7.30 -# -twine upload dist/sqlmath-2026.7.30* -pip install sqlmath==2026.7.30 -``` - - -

-### sqlite upgrade -- goto https://www.sqlite.org/changes.html -```shell - (set -e - # - # lgbm - sh jslint_ci.sh shRollupUpgrade "v4.5.0" "v4.6.0" ".ci.sh sqlmath_base.h" - # - # sqlite - sh jslint_ci.sh shRollupUpgrade "3.50.3" "3.50.4" ".ci.sh sqlmath_external_sqlite.c" - sh jslint_ci.sh shRollupUpgrade "3500300" "3500400" ".ci.sh sqlmath_external_sqlite.c" - # - # shSqlmathUpdate - read -p "Press Enter to shSqlmathUpdate:" - sh jslint_ci.sh shSqlmathUpdate - ) -``` diff --git a/README.md b/README.md index 358836f622..e69de29bb2 100644 --- a/README.md +++ b/README.md @@ -1,671 +0,0 @@ -# sqlmath - -**SQLite with data-science superpowers.** Run statistics, machine learning, and analytics directly in SQL queries — from Python, JavaScript, or the browser. - -[![PyPI](https://img.shields.io/badge/PyPI-Install_Package-3775A9?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/sqlmath/) -[![npm](https://img.shields.io/badge/npm-Install_Package-CB3837?style=for-the-badge&logo=npm&logoColor=white)](https://www.npmjs.com/package/sqlmath) -[![Demo](https://img.shields.io/badge/Demo-Try_Live-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white)](https://sqlmath.github.io/sqlmath/index.html) -[![API Docs](https://img.shields.io/badge/Docs-API_Reference-blue?style=for-the-badge&logo=readthedocs&logoColor=white)](https://sqlmath.github.io/sqlmath/apidoc.html) - -# Status - -| Branch | [master
(v2026.7.30)](https://github.com/sqlmath/sqlmath/tree/master) | [beta
(Web Demo)](https://github.com/sqlmath/sqlmath/tree/beta) | [alpha
(Development)](https://github.com/sqlmath/sqlmath/tree/alpha) | -|--:|:--:|:--:|:--:| -| CI | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Amaster) | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=beta)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Abeta) | [![ci](https://github.com/sqlmath/sqlmath/actions/workflows/ci.yml/badge.svg?branch=alpha)](https://github.com/sqlmath/sqlmath/actions?query=branch%3Aalpha) | -| Coverage | [![coverage](https://sqlmath.github.io/sqlmath/branch-master/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-master/.artifact/coverage/index.html) | [![coverage](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/coverage/index.html) | [![coverage](https://sqlmath.github.io/sqlmath/branch-alpha/.artifact/coverage/coverage_badge.svg)](https://sqlmath.github.io/sqlmath/branch-alpha/.artifact/coverage/index.html) | -| Demo | [](https://sqlmath.github.io/sqlmath/branch-master/index.html) | [](https://sqlmath.github.io/sqlmath/branch-beta/index.html) | [](https://sqlmath.github.io/sqlmath/branch-alpha/index.html) | -| Artifacts | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-master/.artifact) | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-beta/.artifact) | [](https://github.com/sqlmath/sqlmath/tree/gh-pages/branch-alpha/.artifact) | - - -

-# Table of Contents - -1. [Web Demo](#web-demo) - -2. [Why sqlmath?](#why-sqlmath) - -3. [Quickstart](#quickstart) - - [Python](#python) - - [JavaScript (Node.js)](#javascript-nodejs) - - [Browser (WebAssembly)](#browser-webassembly) - -4. [Built-in SQL Functions](#built-in-sql-functions) - - [Math](#math) - - [Statistics (Aggregate)](#statistics-aggregate) - - [Statistics (Scalar)](#statistics-scalar) - - [Arrays](#arrays) - - [Date/Time](#datetime) - - [Time Series / Signal Processing](#time-series--signal-processing) - - [Compression](#compression) - - [Type Casting](#type-casting) - - [Cryptography](#cryptography) - -5. [Machine Learning with LightGBM](#machine-learning-with-lightgbm) - - [Training from a Table](#training-from-a-table) - - [Prediction](#prediction) - - [Real-World Example: Credit Card Fraud Detection](#real-world-example-credit-card-fraud-detection) - - [LightGBM Functions](#lightgbm-functions) - -6. [Use Cases](#use-cases) - - [Financial Data Analysis](#financial-data-analysis) - - [Embedded ML Pipelines](#embedded-ml-pipelines) - - [Data Compression & Hashing](#data-compression--hashing) - -7. [Why sqlmath vs Alternatives?](#why-sqlmath-vs-alternatives) - -8. [Python API Reference](#python-api-reference) - - [db_exec()](#db_exec) - -9. [Node.js API Reference](#nodejs-api-reference) - -10. [Platform Notes](#platform-notes) - -11. [Building from Source](#building-from-source) - - [Prerequisites](#prerequisites) - - [Build](#build) - - [Run Tests](#run-tests) - - [Serve Demo Locally](#serve-demo-locally) - -12. [Package Listing](#package-listing) - -13. [Changelog](#changelog) - -14. [License](#license) - -15. [Devops Instruction](#devops-instruction) - - [python pypi publish](#python-pypi-publish) - - [sqlite upgrade](#sqlite-upgrade) - - -

-# Web Demo -- https://sqlmath.github.io/sqlmath/index.html - -[![screenshot](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_browser__2fsqlmath_2fbranch-beta_2findex.html.png)](https://sqlmath.github.io/sqlmath/index.html) - - -

-# Why sqlmath? - -SQLite is everywhere — it's the most deployed database in the world. But it lacks the statistical and ML functions needed for data-science. sqlmath fixes that. - -**What you get:** -- **20+ built-in functions** for math, statistics, arrays, dates, and compression -- **LightGBM integration** — train and predict ML models directly in SQL -- **Zero dependencies** — single binary, no external libraries needed -- **Multi-platform** — Python, Node.js, browser (WebAssembly) - - -

-# Quickstart - - -

-### Python - -```shell -pip install sqlmath -``` - -```python -from sqlmath import db_open, db_exec, db_close - -# Open an in-memory database -db = db_open(":memory:") - -# Create sample data -db_exec(db=db, sql=""" - CREATE TABLE prices (symbol TEXT, price REAL); - INSERT INTO prices VALUES ('AAPL', 150.0), ('GOOGL', 140.0), ('MSFT', 380.0); -""") - -# Use built-in statistical functions -result = db_exec(db=db, sql=""" - SELECT - AVG(price) AS mean_price, - MEDIAN2(price) AS median_price, - STDEV(price) AS stdev_price - FROM prices -""") -print(result) -# [[{'mean_price': 223.33, 'median_price': 150.0, 'stdev_price': 135.77}]] - -db_close(db) -``` - - -

-### JavaScript (Node.js) - -```shell -npm install sqlmath -``` - -```javascript -import { dbOpenAsync, dbExecAsync, dbCloseAsync } from "sqlmath"; - -const db = await dbOpenAsync({ filename: ":memory:" }); - -await dbExecAsync({ - db, - sql: ` - CREATE TABLE prices (symbol TEXT, price REAL); - INSERT INTO prices VALUES ('AAPL', 150.0), ('GOOGL', 140.0), ('MSFT', 380.0); - ` -}); - -const result = await dbExecAsync({ - db, - sql: "SELECT AVG(price) AS mean_price FROM prices" -}); -console.log(result); - -await dbCloseAsync(db); -``` - - -

-### Browser (WebAssembly) - -Try it live: **[sqlmath.github.io/sqlmath](https://sqlmath.github.io/sqlmath/index.html)** - - -

-# Built-in SQL Functions - - -

-### Math - -| Function | Description | -|----------|-------------| -| `cot(x)` | Cotangent | -| `coth(x)` | Hyperbolic cotangent | -| `fmod(x, y)` | Floating-point modulo | -| `squared(x)` | Square of x | -| `squaredwithsign(x)` | Square preserving sign (negative input → negative output) | -| `sqrtwithsign(x)` | Square root preserving sign | -| `normalizewithsqrt(x)` | Normalize using square root | -| `normalizewithsquared(x)` | Normalize using square | - - -

-### Statistics (Aggregate) - -| Function | Description | -|----------|-------------| -| `MEDIAN2(x)` | Median value (aggregate) | -| `PERCENTILE(x, p)` | Percentile at p (aggregate) | -| `QUANTILE(x, q)` | Quantile at q (aggregate) | -| `STDEV(x)` | Standard deviation (sample, window-capable) | - - -

-### Statistics (Scalar) - -| Function | Description | -|----------|-------------| -| `marginoferror95(p, n)` | 95% margin of error: sqrt(p*(1-p)/n) | -| `random1()` | Random float in [0, 1) | - - -

-### Arrays - -| Function | Description | -|----------|-------------| -| `doublearray_array(...)` | Create double array from values | -| `doublearray_extract(arr, i)` | Extract element at index | -| `doublearray_jsonfrom(json)` | Create array from JSON | -| `doublearray_jsonto(arr)` | Convert array to JSON | - - -

-### Date/Time - -sqlmath extends SQLite's date functions with integer-format conversions (YYYYMMDDHHMMSS). - - -

-### Time Series / Signal Processing - -| Function | Description | -|----------|-------------| -| `WIN_SINEFIT2(...)` | Fit sine wave to data (aggregate window function) | -| `SINEFIT_REFITLAST(...)` | Refit sine wave with new data point | - - -

-### Compression - -| Function | Description | -|----------|-------------| -| `GZIP_COMPRESS(blob)` | Compress BLOB with gzip | -| `GZIP_UNCOMPRESS(blob)` | Decompress gzip BLOB | - -> **Note:** Input must be BLOB. Use `CAST(text AS BLOB)` for text data. - - -

-### Type Casting - -| Function | Description | -|----------|-------------| -| `castrealornull(x)` | Cast to REAL or NULL | -| `castrealorzero(x)` | Cast to REAL or 0.0 | -| `casttextorempty(x)` | Cast to TEXT or '' | -| `roundorzero(x, n)` | Round or return 0 | - - -

-### Cryptography - -| Function | Description | -|----------|-------------| -| `sha256(data)` | SHA-256 hash (returns BLOB) | - -```sql -SELECT HEX(sha256('hello')) AS hash; --- 2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824 -``` - - -

-# Machine Learning with LightGBM - -sqlmath embeds [LightGBM](https://lightgbm.readthedocs.io/) for gradient boosting directly in SQL queries. Train models on your data without leaving SQL — no data shuffle to Python needed. - -> **Python users:** You must call `lgbm_dlopen()` before using any `lgbm_*` functions. See [Platform Notes](#platform-notes) for details. - - -

-### Training from a Table - -`LGBM_TRAINFROMTABLE` is an **aggregate function** — it consumes rows like `SUM()` or `AVG()`, but outputs a trained model BLOB. - -```sql --- Create a table to store the model -CREATE TABLE model_store (model BLOB); - --- Train directly from your data table -INSERT INTO model_store(model) -SELECT - LGBM_TRAINFROMTABLE( - -- Training parameters - ( - 'objective=binary' - || ' learning_rate=0.1' - || ' metric=auc' - || ' num_leaves=31' - || ' verbosity=0' - ), - 50, -- num_iterations - 10, -- eval_step (early stopping check interval) - 'max_bin=15', -- data parameters - NULL, -- reference dataset (NULL for training set) - -- Columns: first column MUST be the label - label, feature1, feature2, feature3, feature4 - ) -FROM training_data; -``` - - -

-### Prediction - -```sql --- Predict on new data using the stored model -SELECT - id, - LGBM_PREDICTFORTABLE( - (SELECT model FROM model_store), - 0, -- predict_type (0 = normal probability) - 0, -- start_iteration - 50, -- num_iterations - '', -- prediction parameters - feature1, feature2, feature3, feature4 - ) AS prediction -FROM test_data; -``` - - -

-### Real-World Example: Credit Card Fraud Detection - -From the [Kaggle notebook](https://www.kaggle.com/code/kaizhu256/sql-is-all-you-need) — training on 284,807 transactions with 0.17% fraud rate: - -```python -from sqlmath import db_open, db_exec, db_table_import - -db = db_open("fraud.sqlite") - -# Import Kaggle credit card dataset -db_table_import(db=db, filename="creditcard.csv", table_name="transactions", mode="csv") - -# Split 80/20 train/test -db_exec(db=db, sql=""" - CREATE TABLE train AS SELECT * FROM transactions WHERE RANDOM() % 5 != 0; - CREATE TABLE test AS SELECT * FROM transactions WHERE RANDOM() % 5 = 0; -""") - -# Train with automatic class imbalance handling -db_exec(db=db, sql=""" - CREATE TABLE model_store (model BLOB); - - INSERT INTO model_store(model) - SELECT - LGBM_TRAINFROMTABLE( - ( - 'objective=binary' - || ' learning_rate=0.05' - || ' metric=auc' - || ' is_unbalance=true' -- Auto-weight minority class - || ' num_leaves=31' - || ' verbosity=0' - ), - 100, -- num_iterations - 10, -- eval_step - 'max_bin=255', -- data_params - NULL, -- reference - -- First column = label, rest = features (V1-V28 + Amount) - Class, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, - V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, - V21, V22, V23, V24, V25, V26, V27, V28, Amount - ) - FROM train; -""") -``` - - -

-### LightGBM Functions - -| Function | Type | Description | -|----------|------|-------------| -| `lgbm_dlopen(path)` | scalar | Load LightGBM library (required in Python) | -| `LGBM_TRAINFROMTABLE(params, n_iter, eval, data_params, ref, label, ...)` | aggregate | Train model from table columns | -| `LGBM_TRAINFROMFILE(params, n_iter, eval, train_file, data_params, test_file)` | scalar | Train from LibSVM file | -| `LGBM_PREDICTFORTABLE(model, type, start, n, params, ...)` | window | Predict per row | -| `LGBM_PREDICTFORFILE(model, type, start, n, params, file, header, out)` | scalar | Predict and save to file | -| `lgbm_extract(result, key)` | scalar | Extract value from result | - -**Complete Example:** - -[![Kaggle](https://img.shields.io/badge/Kaggle-SQL_is_All_You_Need-20BEFF?style=for-the-badge&logo=kaggle&logoColor=white)](https://www.kaggle.com/code/kaizhu256/sql-is-all-you-need) - -Full notebook with fraud detection, intraday trading signals, and model persistence. - -**Kaggle Environment:** -- Linux x64, Python 3.12, Node.js 24 -- Datasets: [Credit Card Fraud](https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud) (284K transactions), SPY intraday OHLCV -- sqlmath installs via `pip install sqlmath==2026.7.30` - - -

-# Use Cases - - -

-### Financial Data Analysis - -```python -from sqlmath import db_open, db_exec, db_close, db_table_import - -db = db_open(":memory:") - -# Import OHLCV data -db_table_import(db=db, table_name="prices", filename="prices.csv", mode="csv") - -# Calculate rolling statistics in SQL -result = db_exec(db=db, sql=""" - SELECT - date, - close, - AVG(close) OVER (ORDER BY date ROWS 20 PRECEDING) AS sma_20, - STDEV(close) OVER (ORDER BY date ROWS 20 PRECEDING) AS volatility, - PERCENTILE(volume, 50) OVER (ORDER BY date ROWS 5 PRECEDING) AS median_volume - FROM prices - ORDER BY date DESC - LIMIT 10 -""") - -db_close(db) -``` - - -

-### Embedded ML Pipelines - -Train and deploy models without data movement: - -```python -# 1. Load data into SQLite -db_table_import(db=db, table_name="features", filename="training_data.csv", mode="csv") - -# 2. Train model in-database -db_exec(db=db, sql=""" - CREATE TABLE models AS - SELECT LGBM_TRAINFROMTABLE(...) AS model FROM features -""") - -# 3. Score new data in-database -db_exec(db=db, sql=""" - SELECT id, LGBM_PREDICTFORTABLE(model, ...) AS score - FROM new_data, models -""") - -# 4. Export results -db_file_save(db=db, filename="scored_data.sqlite") -``` - - -

-### Data Compression & Hashing - -```sql --- Compress large text fields -UPDATE documents SET - content_compressed = GZIP_COMPRESS(CAST(content AS BLOB)); - --- Generate content hashes for deduplication -SELECT HEX(sha256(content)) AS content_hash, COUNT(*) -FROM documents -GROUP BY content_hash -HAVING COUNT(*) > 1; -``` - - -

-# Why sqlmath vs Alternatives? - -| Feature | sqlmath | pandas | DuckDB | sqlite3 | -|---------|---------|--------|--------|---------| -| In-database ML | ✅ LightGBM | ❌ | ❌ | ❌ | -| Zero data shuffle | ✅ | ❌ | ✅ | ✅ | -| Statistical functions | ✅ 20+ | ✅ | ✅ | ❌ | -| Browser support | ✅ WASM | ❌ | ❌ | ❌ | -| Single file deployment | ✅ | ❌ | ✅ | ✅ | -| Memory efficiency | ✅ Streaming | ❌ In-memory | ✅ | ✅ | - -**Choose sqlmath when:** -- You need ML training/inference inside the database -- Your data is already in SQLite -- You want browser-based analytics (WebAssembly) -- You need statistical functions beyond basic SQL - -**Choose pandas when:** -- You need complex data transformations -- Interactive exploration with rich visualization -- Your workflow is Python-centric - -**Choose DuckDB when:** -- You need fast analytical queries on large datasets -- You want pandas DataFrame interop -- OLAP workloads are primary - - -

-# Python API Reference - -```python -from sqlmath import ( - db_open, # Open database - db_close, # Close database - db_exec, # Execute SQL, return results - db_exec_and_return_lastblob, # Execute SQL, return last blob - db_file_load, # Load database from file - db_file_save, # Save database to file - db_table_import, # Import data into table - db_noop, # No-op for testing -) -``` - - -

-### db_exec() - -```python -result = db_exec( - db=db, # Database connection - sql="SELECT ...", # SQL statement(s) - bind_list=[...], # Bind parameters (list or dict) - response_type=None, # None (default) returns list-of-dicts - # "list" | "lastblob" | "arraybuffer" -) -# Default: [[{'col1': val1, 'col2': val2}, ...]] -``` - - -

-# Node.js API Reference -- https://sqlmath.github.io/sqlmath/apidoc.html - -[![screenshot](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_browser__2f.artifact_2fapidoc.html.png)](https://sqlmath.github.io/sqlmath/apidoc.html) - - -

-# Platform Notes - -The JavaScript (Node.js) binding is more mature than Python. Key differences: - -| Feature | Node.js | Python | -|---------|---------|--------| -| Async vs sync api | **async only:** `result = await dbExecAsync({...})` | **sync only:** `result = db_exec(...)` | -| Connection pooling | ✅ `db = await dbOpenAsync({threadCount: 4, ...})` | ❌ `db = db_open(...)` | -| Type hints | N/A | ❌ Not yet | -| Context manager | N/A | ❌ `with db_open()` not supported | - -> **Note:** The Python wrapper is a work in progress. Contributions welcome! - - -

-# Building from Source - - -

-### Prerequisites - -- Node.js 24+ -- Python 3.12+ -- C compiler (gcc, clang, or MSVC) - - -

-### Build - -```shell -#!/bin/sh - -# git clone sqlmath repo -git clone https://github.com/sqlmath/sqlmath --branch=beta --single-branch -cd sqlmath - -# Build native binary -npm run test2 - -# Build WebAssembly (optional) -sh jslint_ci.sh shCiBuildWasm -``` - - -

-### Run Tests - -```shell -# Full test suite (includes full clean-build) -npm run test2 - -# Full test suite (includes partial re-build of modified files) -npm run test - -# Quick test (skip build) -npm run test --fast -``` - - -

-### Serve Demo Locally - -```shell -PORT=8080 sh jslint_ci.sh shHttpFileServer -# Open http://localhost:8080/index.html -``` - - -

-# Package Listing -![screenshot_package_listing.svg](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_package_listing.svg) - - -

-# Changelog -- [Full CHANGELOG.md](CHANGELOG.md) - -![screenshot_changelog.svg](https://sqlmath.github.io/sqlmath/branch-beta/.artifact/screenshot_changelog.svg) - - -

-# License -- [sqlite](https://github.com/sqlite/sqlite) is under [public domain](https://www.sqlite.org/copyright.html). -- [jslint](https://github.com/jslint-org/jslint) is under [Unlicense License](https://github.com/jslint-org/jslint/blob/master/LICENSE). -- [zlib](https://github.com/madler/zlib) is under [zlib License](https://github.com/madler/zlib/blob/v1.3.1/LICENSE). -- [cpplint.py](cpplint.py) is under [3-Clause BSD License](https://github.com/cpplint/cpplint/blob/2.0.0/LICENSE). -- [indent.exe](indent.exe) is under [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.txt). -- Everything else is under MIT License. - - -

-# Devops Instruction - - -

-### python pypi publish -```shell -python -m build -# -twine upload --repository testpypi dist/sqlmath-2026.7.30* -py -m pip install --index-url https://test.pypi.org/simple/ sqlmath==2026.7.30 -# -twine upload dist/sqlmath-2026.7.30* -pip install sqlmath==2026.7.30 -``` - - -

-### sqlite upgrade -- goto https://www.sqlite.org/changes.html -```shell - (set -e - # - # lgbm - sh jslint_ci.sh shRollupUpgrade "v4.5.0" "v4.6.0" ".ci.sh sqlmath_base.h" - # - # sqlite - sh jslint_ci.sh shRollupUpgrade "3.50.3" "3.50.4" ".ci.sh sqlmath_external_sqlite.c" - sh jslint_ci.sh shRollupUpgrade "3500300" "3500400" ".ci.sh sqlmath_external_sqlite.c" - # - # shSqlmathUpdate - read -p "Press Enter to shSqlmathUpdate:" - sh jslint_ci.sh shSqlmathUpdate - ) -``` diff --git a/asset_image_folder_open_solid.svg b/asset_image_folder_open_solid.svg deleted file mode 100644 index 99d403c812..0000000000 --- a/asset_image_folder_open_solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/asset_image_github_brands.svg b/asset_image_github_brands.svg deleted file mode 100644 index 7870c06dc2..0000000000 --- a/asset_image_github_brands.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/asset_image_logo_256.html b/asset_image_logo_256.html deleted file mode 100644 index 1e408f0ea0..0000000000 --- a/asset_image_logo_256.html +++ /dev/null @@ -1,46 +0,0 @@ - - - -logo - - - - - - -
-
- SQL  -
- MATH -
-
- - diff --git a/asset_image_logo_256.png b/asset_image_logo_256.png deleted file mode 100644 index c666de1a4b..0000000000 Binary files a/asset_image_logo_256.png and /dev/null differ diff --git a/asset_image_logo_256.svg b/asset_image_logo_256.svg deleted file mode 100644 index b43a0216a5..0000000000 --- a/asset_image_logo_256.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - diff --git a/asset_sqlmath_external_rollup.js b/asset_sqlmath_external_rollup.js deleted file mode 100644 index 0a2d90ba29..0000000000 --- a/asset_sqlmath_external_rollup.js +++ /dev/null @@ -1,10719 +0,0 @@ -/*jslint-disable*/ -/* -shRollupFetch -{ - "fetchList": [ - { - "url": "https://github.com/codemirror/codemirror5/blob/5.65.10/codemirror.js", - "url2": "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.10/codemirror.js" - }, - { - "url": "https://github.com/codemirror/codemirror5/blob/5.65.10/addon/edit/matchbrackets.js" - }, - { - "url": "https://github.com/codemirror/codemirror5/blob/5.65.10/addon/edit/trailingspace.js" - }, - { - "url": "https://github.com/codemirror/codemirror5/blob/5.65.10/addon/selection/active-line.js" - }, - { - "url": "https://github.com/codemirror/codemirror5/blob/5.65.10/mode/sql/sql.js" - } - ], - "replaceList": [] -} -*/ - - -/* -repo https://github.com/codemirror/codemirror5/tree/5.65.10 -committed 2022-11-20T15:35:33Z -*/ - - -/* -file https://github.com/codemirror/codemirror5/blob/5.65.10/codemirror.js -*/ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/LICENSE - -// This is CodeMirror (https://codemirror.net/5), 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 = global || self, 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\/(\d+)/.exec(userAgent); - var chrome_version = chrome && +chrome[1]; - 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 = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2); - 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(doc) { - // 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 = doc.activeElement; - } catch(e) { - activeElement = doc.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 doc(cm) { return cm.display.wrapper.ownerDocument } - - function win(cm) { return doc(cm).defaultView } - - 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; - this.f = null; - this.time = 0; - this.handler = bind(this.onTimeout, this); - }; - Delayed.prototype.onTimeout = function (self) { - self.id = 0; - if (self.time <= +new Date) { - self.f(); - } else { - setTimeout(self.handler, self.time - +new Date); - } - }; - Delayed.prototype.set = function (ms, f) { - this.f = f; - var time = +new Date + ms; - if (!this.id || time < this.time) { - clearTimeout(this.id); - this.id = setTimeout(this.handler, ms); - this.time = time; - } - }; - - 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 = 50; - - // 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}, sel_mouse = {origin: "*mouse"}, 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`. Supports `from` being greater than `to`. - function findFirst(pred, from, to) { - // At any point we are certain `to` satisfies `pred`, don't know - // whether `from` does. - var dir = from > to ? -1 : 1; - for (;;) { - if (from == to) { return from } - var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); - if (mid == from) { return pred(mid) ? from : to } - if (pred(mid)) { to = mid; } - else { from = mid + dir; } - } - } - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) { return f(from, to, "ltr", 0) } - 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", i); - 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, isRTL = direction == "rtl" ? 1 : 0; - 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)); at += isRTL; } - 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)); - at += isRTL; - pos = j$2; - } else { ++j$2; } - } - if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } - } - } - if (direction == "ltr") { - 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 - } - - // 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 - } - - // Known modes, by name and by MIME - var modes = {}, 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 start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.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) - }; - StringStream.prototype.baseToken = function () { - var oracle = this.lineOracle; - return oracle && oracle.baseToken(this.pos) - }; - - // 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 - } - - 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; - this.baseTokens = null; - this.baseTokenPos = 1; - }; - - 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.baseToken = function (n) { - if (!this.baseTokens) { return null } - while (this.baseTokens[this.baseTokenPos] <= n) - { this.baseTokenPos += 2; } - var type = this.baseTokens[this.baseTokenPos + 1]; - return {type: type && type.replace(/( |^)overlay .*/, ""), - size: this.baseTokens[this.baseTokenPos] - n} - }; - - 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 ) { - context.baseTokens = st; - 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); - context.state = state; - context.baseTokens = null; - context.baseTokenPos = 1; - }; - - for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); - - 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); - } - - // Optimize some code when these features are not used. - var sawReadOnlySpans = false, 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, op) { - var inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet)); - if (inThisOp && line.markedSpans && inThisOp.has(line.markedSpans)) { - line.markedSpans.push(span); - } else { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - if (inThisOp) { inThisOp.add(line.markedSpans); } - } - 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) } - - function collapsedSpanAround(line, ch) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) { for (var i = 0; i < sps.length; ++i) { - var sp = sps[i]; - if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && - (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } - } } - return found - } - - // 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; - } - }); - } - - // 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 = {}, 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: 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, css, attributes) { - 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 || attributes) { - var fullStyle = style || ""; - if (startStyle) { fullStyle += startStyle; } - if (endStyle) { fullStyle += endStyle; } - var token = elt("span", [content], fullStyle, css); - if (attributes) { - for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") - { token.setAttribute(attr, attributes[attr]); } } - } - return builder.content.appendChild(token) - } - builder.content.appendChild(content); - } - - // Change some spaces to NBSP to prevent the browser from collapsing - // trailing spaces at the end of a line when rendering text (issue #1362). - 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, css, attributes) { - 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, css, attributes) } - inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes); - 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, collapsed, attributes; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = css = ""; - attributes = null; - 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); } - // support for the old title property - // https://github.com/codemirror/CodeMirror/pull/5673 - if (m.title) { (attributes || (attributes = {})).title = m.title; } - if (m.attributes) { - for (var attr in m.attributes) - { (attributes || (attributes = {}))[attr] = m.attributes[attr]; } - } - 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 : "", css, attributes); - } - 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")); - gutterWrap.setAttribute("aria-hidden", "true"); - 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.display.gutterSpecs.length; ++k) { - var id = cm.display.gutterSpecs[k].className, 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; } - var isWidget = classTest("CodeMirror-linewidget"); - for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { - next = node.nextSibling; - if (isWidget.test(node.className)) { 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" + (widget.className ? " " + widget.className : "")); - 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", "CodeMirror-line-like")); - 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} } - if (lineView.rest) { - 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(doc) { - // 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 -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft)) } - return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft - } - function pageScrollY(doc) { - if (chrome && android) { return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop)) } - return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop - } - - function widgetTopHeight(lineObj) { - var ref = visualLine(lineObj); - var widgets = ref.widgets; - var height = 0; - if (widgets) { for (var i = 0; i < widgets.length; ++i) { if (widgets[i].above) - { height += widgetHeight(widgets[i]); } } } - return height - } - - // 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) { - var height = widgetTopHeight(lineObj); - rect.top += height; rect.bottom += height; - } - 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(doc(cm))); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm))); - 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(doc(cm)); - top -= pageScrollY(doc(cm)); - } 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 == 1; - 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 = outside; } - 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, -1, -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, 1, 1) } - if (x < 0) { x = 0; } - - var lineObj = getLine(doc, lineN); - for (;;) { - var found = coordsCharInner(cm, lineObj, lineN, x, y); - var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0)); - if (!collapsed) { return found } - var rangeEnd = collapsed.find(1); - if (rangeEnd.line == lineN) { return rangeEnd } - lineObj = getLine(doc, lineN = rangeEnd.line); - } - } - - function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { - y -= widgetTopHeight(lineObj); - var end = lineObj.text.length; - var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); - end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); - return {begin: begin, end: end} - } - - function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { - if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } - var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; - return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) - } - - // Returns true if the given side of a box is after the given - // coordinates, in top-to-bottom, left-to-right order. - function boxIsAfter(box, x, y, left) { - return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - // Move y into line-local coordinate space - y -= heightAtLine(lineObj); - var preparedMeasure = prepareMeasureForLine(cm, lineObj); - // When directly calling `measureCharPrepared`, we have to adjust - // for the widgets at this line. - var widgetHeight = widgetTopHeight(lineObj); - var begin = 0, end = lineObj.text.length, ltr = true; - - var order = getOrder(lineObj, cm.doc.direction); - // If the line isn't plain left-to-right text, first figure out - // which bidi section the coordinates fall into. - if (order) { - var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) - (cm, lineObj, lineNo, preparedMeasure, order, x, y); - ltr = part.level != 1; - // The awkward -1 offsets are needed because findFirst (called - // on these below) will treat its first bound as inclusive, - // second as exclusive, but we want to actually address the - // characters in the part's range - begin = ltr ? part.from : part.to - 1; - end = ltr ? part.to : part.from - 1; - } - - // A binary search to find the first character whose bounding box - // starts after the coordinates. If we run across any whose box wrap - // the coordinates, store that. - var chAround = null, boxAround = null; - var ch = findFirst(function (ch) { - var box = measureCharPrepared(cm, preparedMeasure, ch); - box.top += widgetHeight; box.bottom += widgetHeight; - if (!boxIsAfter(box, x, y, false)) { return false } - if (box.top <= y && box.left <= x) { - chAround = ch; - boxAround = box; - } - return true - }, begin, end); - - var baseX, sticky, outside = false; - // If a box around the coordinates was found, use that - if (boxAround) { - // Distinguish coordinates nearer to the left or right side of the box - var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; - ch = chAround + (atStart ? 0 : 1); - sticky = atStart ? "after" : "before"; - baseX = atLeft ? boxAround.left : boxAround.right; - } else { - // (Adjust for extended bound, if necessary.) - if (!ltr && (ch == end || ch == begin)) { ch++; } - // To determine which side to associate with, get the box to the - // left of the character and compare it's vertical position to the - // coordinates - sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : - (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? - "after" : "before"; - // Now get accurate coordinates for this place, in order to get a - // base X position - var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure); - baseX = coords.left; - outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0; - } - - ch = skipExtendingChars(lineObj.text, ch, 1); - return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) - } - - function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { - // Bidi parts are sorted left-to-right, and in a non-line-wrapping - // situation, we can take this ordering to correspond to the visual - // ordering. This finds the first part whose end is after the given - // coordinates. - var index = findFirst(function (i) { - var part = order[i], ltr = part.level != 1; - return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), - "line", lineObj, preparedMeasure), x, y, true) - }, 0, order.length - 1); - var part = order[index]; - // If this isn't the first part, the part's start is also after - // the coordinates, and the coordinates aren't on the same line as - // that start, move one part back. - if (index > 0) { - var ltr = part.level != 1; - var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), - "line", lineObj, preparedMeasure); - if (boxIsAfter(start, x, y, true) && start.top > y) - { part = order[index - 1]; } - } - return part - } - - function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { - // In a wrapped line, rtl text on wrapping boundaries can do things - // that don't correspond to the ordering in our `order` array at - // all, so a binary search doesn't work, and we want to return a - // part that only spans one line so that the binary search in - // coordsCharInner is safe. As such, we first find the extent of the - // wrapped line, and then do a flat search in which we discard any - // spans that aren't on the line. - var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); - var begin = ref.begin; - var end = ref.end; - if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } - var part = null, closestDist = null; - for (var i = 0; i < order.length; i++) { - var p = order[i]; - if (p.from >= end || p.to <= begin) { continue } - var ltr = p.level != 1; - var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; - // Weigh against spans ending before this, so that they are only - // picked if nothing ends after - var dist = endX < x ? x - endX + 1e9 : endX - x; - if (!part || closestDist > dist) { - part = p; - closestDist = dist; - } - } - if (!part) { part = order[order.length - 1]; } - // Clip the part to the wrapped line. - if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } - if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } - return part - } - - var measureText; - // Compute the default text height. - function textHeight(display) { - if (display.cachedTextHeight != null) { return display.cachedTextHeight } - if (measureText == null) { - measureText = elt("pre", null, "CodeMirror-line-like"); - // 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], "CodeMirror-line-like"); - 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) { - var id = cm.display.gutterSpecs[i].className; - left[id] = n.offsetLeft + n.clientLeft + gutterLeft; - width[id] = 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$1) { return null } - var coords = coordsChar(cm, x, y), line; - if (forRect && coords.xRel > 0 && (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 } - } - } - - // 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 - } - - function updateSelection(cm) { - cm.display.input.showSelection(cm.display.input.prepareSelection()); - } - - function prepareSelection(cm, primary) { - if ( primary === void 0 ) primary = true; - - var doc = cm.doc, result = {}; - var curFragment = result.cursors = document.createDocumentFragment(); - var selFragment = result.selection = document.createDocumentFragment(); - - var customCursor = cm.options.$customCursor; - if (customCursor) { primary = true; } - for (var i = 0; i < doc.sel.ranges.length; i++) { - if (!primary && 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 (customCursor) { - var head = customCursor(cm, range); - if (head) { drawSelectionCursor(cm, head, curFragment); } - } else 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 (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) { - var charPos = charCoords(cm, head, "div", null, null); - var width = charPos.right - charPos.left; - cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "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"; - } - } - - function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } - - // 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; - var docLTR = doc.direction == "ltr"; - - 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) - } - - function wrapX(pos, dir, side) { - var extent = wrappedLineExtentChar(cm, lineObj, null, pos); - var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; - var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); - return coords(ch, prop)[prop] - } - - var order = getOrder(lineObj, doc.direction); - iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { - var ltr = dir == "ltr"; - var fromPos = coords(from, ltr ? "left" : "right"); - var toPos = coords(to - 1, ltr ? "right" : "left"); - - var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; - var first = i == 0, last = !order || i == order.length - 1; - if (toPos.top - fromPos.top <= 3) { // Single line - var openLeft = (docLTR ? openStart : openEnd) && first; - var openRight = (docLTR ? openEnd : openStart) && last; - var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; - var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; - add(left, fromPos.top, right - left, fromPos.bottom); - } else { // Multiple lines - var topLeft, topRight, botLeft, botRight; - if (ltr) { - topLeft = docLTR && openStart && first ? leftSide : fromPos.left; - topRight = docLTR ? rightSide : wrapX(from, dir, "before"); - botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); - botRight = docLTR && openEnd && last ? rightSide : toPos.right; - } else { - topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); - topRight = !docLTR && openStart && first ? rightSide : fromPos.right; - botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; - botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); - } - add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); - if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } - add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); - } - - if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } - if (cmpCoords(toPos, start) < 0) { start = toPos; } - if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } - if (cmpCoords(toPos, end) < 0) { end = toPos; } - }); - 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 () { - if (!cm.hasFocus()) { onBlur(cm); } - 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.hasFocus()) { - cm.display.input.focus(); - if (!cm.state.focused) { onFocus(cm); } - } - } - - function delayBlurEvent(cm) { - cm.state.delayingBlurEvent = true; - setTimeout(function () { if (cm.state.delayingBlurEvent) { - cm.state.delayingBlurEvent = false; - if (cm.state.focused) { onBlur(cm); } - } }, 100); - } - - function onFocus(cm, e) { - if (cm.state.delayingBlurEvent && !cm.state.draggingText) { 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; - var viewTop = Math.max(0, display.scroller.getBoundingClientRect().top); - var oldHeight = display.lineDiv.getBoundingClientRect().top; - var mustScroll = 0; - for (var i = 0; i < display.view.length; i++) { - var cur = display.view[i], wrapping = cm.options.lineWrapping; - var height = (void 0), width = 0; - if (cur.hidden) { continue } - oldHeight += cur.line.height; - 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; - // Check that lines don't extend past the right of the current - // editor width - if (!wrapping && cur.text.firstChild) - { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; } - } - var diff = cur.line.height - height; - if (diff > .005 || diff < -.005) { - if (oldHeight < viewTop) { mustScroll -= diff; } - updateLineHeight(cur.line, height); - updateWidgetHeight(cur.line); - if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) - { updateWidgetHeight(cur.rest[j]); } } - } - if (width > cm.display.sizerWidth) { - var chWidth = Math.ceil(width / charWidth(cm.display)); - if (chWidth > cm.display.maxLineLength) { - cm.display.maxLineLength = chWidth; - cm.display.maxLine = cur.line; - cm.display.maxLineChanged = true; - } - } - } - if (Math.abs(mustScroll) > 2) { display.scroller.scrollTop += mustScroll; } - } - - // 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) { - var w = line.widgets[i], parent = w.node.parentNode; - if (parent) { w.height = parent.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)} - } - - // 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; - var doc = display.wrapper.ownerDocument; - if (rect.top + box.top < 0) { doScroll = true; } - else if (rect.bottom + box.top > (doc.defaultView.innerHeight || doc.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 - end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; - pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : 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 gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth; - var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace; - var screenw = displayWidth(cm) - display.gutters.offsetWidth; - 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 + gutterSpace - (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.max(0, 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.max(0, 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"); - vert.tabIndex = horiz.tabIndex = -1; - 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.scrollTop = 0; - 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.visibility = this.vert.style.visibility = "hidden"; - this.disableHoriz = new Delayed; - this.disableVert = new Delayed; - }; - - NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { - bar.style.visibility = ""; - 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.visibility = "hidden"; } - 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: 0, // 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 - markArrays: null // Used by addMarkedSpan - }; - pushOperation(cm.curOp); - } - - // Finish an operation, updating the display and signalling delayed events - function endOperation(cm) { - var op = cm.curOp; - if (op) { 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(); } - } - - 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(doc(cm)); - 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); } - } - } - - // 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 () { - for (var i = 0; i < this.events.length; i++) - { signal.apply(null, this.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(doc(cm)); - if (!active || !contains(cm.display.lineDiv, active)) { return null } - var result = {activeElt: active}; - if (window.getSelection) { - var sel = win(cm).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(snapshot.activeElt.ownerDocument)) { return } - snapshot.activeElt.focus(); - if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) && - snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { - var doc = snapshot.activeElt.ownerDocument; - var sel = doc.defaultView.getSelection(), range = doc.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 } - } else if (first) { - update.visible = visibleLines(cm.display, cm.doc, viewport); - } - 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(display) { - var width = display.gutters.offsetWidth; - display.sizer.style.marginLeft = width + "px"; - // Send an event to consumers responding to changes in gutter width. - signalLater(display, "gutterChanged", display); - } - - 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"; - } - - // 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.display); - return true - } - return false - } - - function getGutters(gutters, lineNumbers) { - var result = [], sawLineNumbers = false; - for (var i = 0; i < gutters.length; i++) { - var name = gutters[i], style = null; - if (typeof name != "string") { style = name.style; name = name.className; } - if (name == "CodeMirror-linenumbers") { - if (!lineNumbers) { continue } - else { sawLineNumbers = true; } - } - result.push({className: name, style: style}); - } - if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); } - return result - } - - // Rebuild the gutter elements, ensure the margin to the left of the - // code matches their width. - function renderGutters(display) { - var gutters = display.gutters, specs = display.gutterSpecs; - removeChildren(gutters); - display.lineGutter = null; - for (var i = 0; i < specs.length; ++i) { - var ref = specs[i]; - var className = ref.className; - var style = ref.style; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)); - if (style) { gElt.style.cssText = style; } - if (className == "CodeMirror-linenumbers") { - display.lineGutter = gElt; - gElt.style.width = (display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = specs.length ? "" : "none"; - updateGutterSpace(display); - } - - function updateGutters(cm) { - renderGutters(cm.display); - regChange(cm); - alignHorizontally(cm); - } - - // 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, options) { - 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"); - // See #6982. FIXME remove when this has been fixed for a while in Chrome - if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; } - - // This attribute is respected by automatic translation systems such as Google Translate, - // and may also be respected by tools used by human translators. - d.wrapper.setAttribute('translate', 'no'); - - // 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; - - d.gutterSpecs = getGutters(options.gutters, options.lineNumbers); - renderGutters(d); - - input.init(d); - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, 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) { - // On Chrome 102, viewport updates somehow stop wheel-based - // scrolling. Turning off pointer events during the scroll seems - // to avoid the issue. - if (chrome && chrome_version == 102) { - if (cm.display.chromeScrollHack == null) { cm.display.sizer.style.pointerEvents = "none"; } - else { clearTimeout(cm.display.chromeScrollHack); } - cm.display.chromeScrollHack = setTimeout(function () { - cm.display.chromeScrollHack = null; - cm.display.sizer.style.pointerEvents = ""; - }, 100); - } - var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; - var pixelsPerUnit = wheelPixelsPerUnit; - if (e.deltaMode === 0) { - dx = e.deltaX; - dy = e.deltaY; - pixelsPerUnit = 1; - } - - 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 && pixelsPerUnit != null) { - if (dy && canScrollY) - { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)); } - setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit)); - // 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 && pixelsPerUnit != null) { - var pixels = dy * pixelsPerUnit; - 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 && e.deltaMode !== 0) { - 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) { - 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.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 out = []; - for (var i = 0; i < this.ranges.length; i++) - { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); } - return new Selection(out, this.primIndex) - }; - - Selection.prototype.somethingSelected = function () { - for (var i = 0; i < this.ranges.length; i++) - { if (!this.ranges[i].empty()) { return true } } - return false - }; - - Selection.prototype.contains = function (pos, end) { - if (!end) { end = pos; } - for (var i = 0; i < this.ranges.length; i++) { - var range = this.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(cm, ranges, primIndex) { - var mayTouch = cm && cm.options.selectionsMayTouch; - 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]; - var diff = cmp(prev.to(), cur.from()); - if (mayTouch && !cur.empty() ? diff > 0 : diff >= 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(doc.cm, 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); - cm.options.direction = doc.direction; - 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(prev) { - // 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 = prev ? prev.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 = prev ? prev.maxGeneration : 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) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || - 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(doc.cm, 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(doc.cm, 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) { - this.ranges = []; - for (var i = 0; i < ranges.length; i++) - { this.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(doc.cm, 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 && doc.cm.getOption("readOnly") != "nocursor") - { ensureCursorVisible(doc.cm); } - } - - function setSelectionInner(doc, sel) { - if (sel.equals(doc.sel)) { return } - - doc.sel = sel; - - if (doc.cm) { - doc.cm.curOp.updateInput = 1; - 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 = range.head == range.anchor ? newAnchor : 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(doc.cm, 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; - - // Determine if we should prevent the cursor being placed to the left/right of an atomic marker - // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it - // is with selectLeft/Right - var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft; - var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight; - - if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && - (sp.to == null || (preventCursorRight ? 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 ? preventCursorRight : preventCursorLeft) - { 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 ? preventCursorLeft : preventCursorRight) - { 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) { - if (doc.cm) { doc.cm.curOp.updateInput = 2; } - 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, origin: change.origin}); } - } 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) { - var suppress = doc.cm && doc.cm.state.suppressEdits; - if (suppress && !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 if (suppress) { - source.push(event); - return - } 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); - - if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0))) - { doc.cantEdit = false; } - } - - // 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) { - var assign; - - if (!to) { to = from; } - if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); } - 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) { - this.lines = lines; - this.parent = null; - var height = 0; - for (var i = 0; i < lines.length; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length }, - - // Remove the n lines at offset 'at'. - removeInner: function(at, n) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.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(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(at, lines, height) { - 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; } - }, - - // Used to iterate over a part of the tree. - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - { if (op(this.lines[at])) { return true } } - } - }; - - function BranchChunk(children) { - 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; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size }, - - removeInner: function(at, n) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.removeInner(at, rm); - this.height -= oldHeight - child.height; - if (sz == rm) { this.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(lines) { - for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); } - }, - - insertInner: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0; i < this.children.length; ++i) { - var child = this.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.children.splice(++i, 0, leaf); - leaf.parent = this; - } - child.lines = child.lines.slice(0, remaining); - this.maybeSpill(); - } - break - } - at -= sz; - } - }, - - // When a node has grown, check whether it should be split. - maybeSpill: function() { - 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(at, n, op) { - for (var i = 0; i < this.children.length; ++i) { - var child = this.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) { - if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) - { this[opt] = options[opt]; } } } - this.doc = doc; - this.node = node; - }; - - LineWidget.prototype.clear = function () { - 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) { 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 } - if (!lineIsHidden(this.doc, line)) { 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, 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 - }); - if (cm) { 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 () { - 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.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (cm && !this.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.collapsed && !lineIsHidden(this.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.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) { - if (side == null && this.type == "bookmark") { side = 1; } - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - 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), doc.cm && doc.cm.curOp); - ++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.startStyle || marker.endStyle || marker.css || - marker.attributes || marker.title) - { 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) { - this.markers = markers; - this.primary = primary; - for (var i = 0; i < markers.length; ++i) - { markers[i].parent = this; } - }; - - SharedTextMarker.prototype.clear = function () { - if (this.explicitlyCleared) { return } - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) - { this.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 } - if (lineSep === '') { return lines.join('') } - 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) { - if (!ranges.length) { return } - var out = []; - for (var i = 0; i < ranges.length; i++) - { out[i] = new Range(clipPos(this, ranges[i].anchor), - clipPos(this, ranges[i].head || ranges[i].anchor)); } - if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } - setSelection(this, normalizeSelection(this.cm, 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(this.cm, ranges, ranges.length - 1), options); - }), - - getSelection: function(lineSep) { - var ranges = this.sel.ranges, lines; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, 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 parts = [], ranges = this.sel.ranges; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, ranges[i].from(), ranges[i].to()); - if (lineSep !== false) { sel = sel.join(lineSep || this.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 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.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, 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() { - var this$1 = this; - - this.history = new History(this.history); - linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true); - }, - - 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); - 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) { - if (other instanceof CodeMirror) { other = other.doc; } - if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { - var link = this.linked[i]; - if (link.doc != other) { continue } - this.linked.splice(i, 1); - other.unlinkDoc(this); - detachSharedMarkers(findSharedMarkers(this)); - 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 markAsReadAndPasteIfAllFilesAreRead = function () { - if (++read == n) { - operation(cm, function () { - pos = clipPos(cm.doc, pos); - var change = {from: pos, to: pos, - text: cm.doc.splitLines( - text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())), - origin: "paste"}; - makeChange(cm.doc, change); - setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change)))); - })(); - } - }; - var readTextFromFile = function (file, i) { - if (cm.options.allowDropFileTypes && - indexOf(cm.options.allowDropFileTypes, file.type) == -1) { - markAsReadAndPasteIfAllFilesAreRead(); - return - } - var reader = new FileReader; - reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); }; - reader.onload = function () { - var content = reader.result; - if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { - markAsReadAndPasteIfAllFilesAreRead(); - return - } - text[i] = content; - markAsReadAndPasteIfAllFilesAreRead(); - }; - reader.readAsText(file); - }; - for (var i = 0; i < files.length; i++) { readTextFromFile(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$1){} - } - } - - 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"), editors = []; - for (var i = 0; i < byClass.length; i++) { - var cm = byClass[i].CodeMirror; - if (cm) { editors.push(cm); } - } - if (editors.length) { editors[0].operation(function () { - for (var i = 0; i < editors.length; i++) { f(editors[i]); } - }); } - } - - 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; - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - d.scrollbarsClipped = false; - cm.setSize(); - } - - var keyNames = { - 3: "Pause", 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: "/", 145: "ScrollLock", - 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 224: "Mod", 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", - "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", - "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "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 != "Mod") { 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 } - // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, - // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) - if (event.keyCode == 3 && event.code) { name = event.code; } - 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); - }); - } - - 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) { - if (cm.doc.direction == "rtl") { dir = -dir; } - 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 || cm.doc.direction == "rtl") { - 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 - } - - // 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, "codepoint"); }, - 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(start.ch, 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" } - if (/\'$/.test(name)) - { cm.state.keySeq = null; } - else - { stopSeq.set(50, function () { - if (cm.state.keySeq == seq) { - cm.state.keySeq = null; - cm.display.input.reset(); - } - }); } - if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } - } - return dispatchKeyInner(cm, name, e, handle) - } - - function dispatchKeyInner(cm, name, e, handle) { - 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); - } - - 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; - if (e.target && e.target != cm.display.input.getField()) { return } - cm.curOp.focus = activeElt(doc(cm)); - 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"); } - } - if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand) - { document.execCommand("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 (e.target && e.target != cm.display.input.getField()) { return } - 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, 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"; - win(cm).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) { cm.display.input.onContextMenu(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(doc(cm)); } - - 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; - if (cm.state.delayingBlurEvent) { - if (cm.hasFocus()) { cm.state.delayingBlurEvent = false; } - else { delayBlurEvent(cm); } - } - off(display.wrapper.ownerDocument, "mouseup", dragEnd); - off(display.wrapper.ownerDocument, "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 && !safari) || ie && ie_version == 9) - { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); 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; - on(display.wrapper.ownerDocument, "mouseup", dragEnd); - on(display.wrapper.ownerDocument, "mousemove", mouseMove); - on(display.scroller, "dragstart", dragStart); - on(display.scroller, "drop", dragEnd); - - cm.state.delayingBlurEvent = true; - setTimeout(function () { return display.input.focus(); }, 20); - // IE's approach to draggable - if (display.scroller.dragDrop) { display.scroller.dragDrop(); } - } - - 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) { - if (ie) { delayBlurEvent(cm); } - var display = cm.display, doc$1 = cm.doc; - e_preventDefault(event); - - var ourRange, ourIndex, startSel = doc$1.sel, ranges = startSel.ranges; - if (behavior.addNew && !behavior.extend) { - ourIndex = doc$1.sel.contains(start); - if (ourIndex > -1) - { ourRange = ranges[ourIndex]; } - else - { ourRange = new Range(start, start); } - } else { - ourRange = doc$1.sel.primary(); - ourIndex = doc$1.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$1, new Selection([ourRange], 0), sel_mouse); - startSel = doc$1.sel; - } else if (ourIndex == -1) { - ourIndex = ranges.length; - setSelection(doc$1, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), - {scroll: false, origin: "*mouse"}); - } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { - setSelection(doc$1, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), - {scroll: false, origin: "*mouse"}); - startSel = doc$1.sel; - } else { - replaceOneSelection(doc$1, 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$1, start.line).text, start.ch, tabSize); - var posCol = countColumn(getLine(doc$1, 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$1, 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$1, normalizeSelection(cm, 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] = bidiSimplify(cm, new Range(clipPos(doc$1, anchor), head)); - setSelection(doc$1, normalizeSelection(cm, 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(doc(cm)); - extendTo(cur); - var visible = visibleLines(display, doc$1); - 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; - // If e is null or undefined we interpret this as someone trying - // to explicitly cancel the selection rather than the user - // letting go of the mouse button. - if (e) { - e_preventDefault(e); - display.input.focus(); - } - off(display.wrapper.ownerDocument, "mousemove", move); - off(display.wrapper.ownerDocument, "mouseup", up); - doc$1.history.lastSelOrigin = null; - } - - var move = operation(cm, function (e) { - if (e.buttons === 0 || !e_button(e)) { done(e); } - else { extend(e); } - }); - var up = operation(cm, done); - cm.state.selectingText = up; - on(display.wrapper.ownerDocument, "mousemove", move); - on(display.wrapper.ownerDocument, "mouseup", up); - } - - // Used when mouse-selecting to adjust the anchor to the proper side - // of a bidi jump depending on the visual position of the head. - function bidiSimplify(cm, range) { - var anchor = range.anchor; - var head = range.head; - var anchorLine = getLine(cm.doc, anchor.line); - if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } - var order = getOrder(anchorLine); - if (!order) { return range } - var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; - if (part.from != anchor.ch && part.to != anchor.ch) { return range } - var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); - if (boundary == 0 || boundary == order.length) { return range } - - // Compute the relative visual position of the head compared to the - // anchor (<0 is to the left, >0 to the right) - var leftSide; - if (head.line != anchor.line) { - leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; - } else { - var headIndex = getBidiPartAt(order, head.ch, head.sticky); - var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); - if (headIndex == boundary - 1 || headIndex == boundary) - { leftSide = dir < 0; } - else - { leftSide = dir > 0; } - } - - var usePart = order[boundary + (leftSide ? -1 : 0)]; - var from = leftSide == (usePart.level == 1); - var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; - return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) - } - - - // 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; - if (e.touches) { - mX = e.touches[0].clientX; - mY = e.touches[0].clientY; - } else { - try { mX = e.clientX; mY = e.clientY; } - catch(e$1) { 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.display.gutterSpecs.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && g.getBoundingClientRect().right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.display.gutterSpecs[i]; - signal(cm, type, cm, line, gutter.className, 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 } - if (!captureRightClick) { 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\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/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("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true); - option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true); - option("rtlMoveVisually", !windows); - option("wholeLineUpdateBefore", true); - - option("theme", "default", function (cm) { - themeChanged(cm); - updateGutters(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, val) { - cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers); - updateGutters(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, val) { - cm.display.gutterSpecs = getGutters(cm.options.gutters, val); - updateGutters(cm); - }, true); - option("firstLineNumber", 1, updateGutters, true); - option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("resetSelectionOnContextMenu", true); - option("lineWiseCopyCut", true); - option("pasteLinesPerSelection", true); - option("selectionsMayTouch", false); - - option("readOnly", false, function (cm, val) { - if (val == "nocursor") { - onBlur(cm); - cm.display.input.blur(); - } - cm.display.input.readOnlyChanged(val); - }); - - option("screenReaderLabel", null, function (cm, val) { - val = (val === '') ? null : val; - cm.display.input.screenReaderLabelChanged(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); - option("phrases", null); - } - - 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); - - var doc = options.value; - if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } - else if (options.mode) { doc.modeOption = options.mode; } - this.doc = doc; - - var input = new CodeMirror.inputStyles[options.inputStyle](this); - var display = this.display = new Display(place, doc, input, options); - display.wrapper.CodeMirror = 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: -1, cutIncoming: -1, // 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(function () { - if (this$1.hasFocus() && !this$1.state.focused) { onFocus(this$1); } - }, 20); } - else - { onBlur(this); } - - for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) - { optionHandlers[opt](this, options[opt], Init); } } - maybeUpdateLineNumberWidth(this); - if (options.finishInit) { options.finishInit(this); } - for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); } - 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. - on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); - on(d.input.getField(), "contextmenu", function (e) { - if (!d.scroller.contains(e.target)) { 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) && !clickInGutter(cm, 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 recent = +new Date - 200; - var paste = origin == "paste" || cm.state.pasteIncoming > recent; - var textLines = splitLinesAuto(inserted), multiPaste = null; - // When pasting 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 = cm.curOp.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 (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == textLines.join("\n")) - { from = to = Pos(from.line, 0); } - } - var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, - origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")}; - makeChange(cm.doc, changeEvent); - signalLater(cm, "inputRead", cm, changeEvent); - } - if (inserted && !paste) - { triggerElectric(cm, inserted); } - - ensureCursorVisible(cm); - if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; } - cm.curOp.typing = true; - cm.state.pasteIncoming = cm.state.cutIncoming = -1; - } - - function handlePaste(e, cm) { - var pasted = e.clipboardData && e.clipboardData.getData("Text"); - if (pasted) { - e.preventDefault(); - if (!cm.isReadOnly() && !cm.options.disableInput && cm.hasFocus()) - { 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, autocorrect, autocapitalize) { - field.setAttribute("autocorrect", autocorrect ? "" : "off"); - field.setAttribute("autocapitalize", autocapitalize ? "" : "off"); - field.setAttribute("spellcheck", !!spellcheck); - } - - function hiddenTextarea() { - var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-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(){win(this).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 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.state.modeGen++; - regChange(this); - 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 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.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; - for (var j = start; j < end; ++j) - { indentLine(this, j, how); } - var newRanges = this.doc.sel.ranges; - if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) - { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } - } else if (range.head.line > end) { - indentLine(this, range.head.line, how, true); - end = range.head.line; - if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); } - } - } - }), - - // 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 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) && 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 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.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 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, cur, "div"); - if (x == null) { x = coords.left; } - else { coords.left = x; } - cur = findPosV(this, 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(doc(this)) }, - 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.display); - if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping) - { estimateLineHeights(this); } - signal(this, "refresh", this); - }), - - swapDoc: methodOp(function(doc) { - var old = this.doc; - old.cm = null; - // Cancel the current text selection if any (#5821) - if (this.state.selectingText) { this.state.selectingText(); } - 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 - }), - - phrase: function(phraseText) { - var phrases = this.options.phrases; - return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText - }, - - 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 "codepoint", "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); - var lineDir = visually && doc.direction == "rtl" ? -dir : dir; - function findNextLine() { - var l = pos.line + lineDir; - 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 (unit == "codepoint") { - var ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1)); - if (isNaN(ch)) { - next = null; - } else { - var astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF; - next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir); - } - } else 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, lineDir); } - else - { return false } - } else { - pos = next; - } - return true - } - - if (unit == "char" || unit == "codepoint") { - 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, win(cm).innerHeight || doc(cm).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; - div.contentEditable = true; - disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize); - - function belongsToInput(e) { - for (var t = e.target; t; t = t.parentNode) { - if (t == div) { return true } - if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) { break } - } - return false - } - - on(div, "paste", function (e) { - if (!belongsToInput(e) || 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 (!belongsToInput(e) || 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 = activeElt(div.ownerDocument); - 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.screenReaderLabelChanged = function (label) { - // Label for screenreaders, accessibility - if(label) { - this.div.setAttribute('aria-label', label); - } else { - this.div.removeAttribute('aria-label'); - } - }; - - ContentEditableInput.prototype.prepareSelection = function () { - var result = prepareSelection(this.cm, false); - result.focus = activeElt(this.div.ownerDocument) == this.div; - 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.getSelection = function () { - return this.cm.display.wrapper.ownerDocument.getSelection() - }; - - ContentEditableInput.prototype.showPrimarySelection = function () { - var sel = this.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 = this.getSelection(); - this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; - this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; - }; - - ContentEditableInput.prototype.selectionInEditor = function () { - var sel = this.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() || activeElt(this.div.ownerDocument) != this.div) - { 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 this$1 = this; - - var input = this; - if (this.selectionInEditor()) - { setTimeout(function () { return this$1.pollSelection(); }, 20); } - 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 = this.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 = this.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.display.gutterSpecs.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 || this.composing) { 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(), extraLinebreak = false; - function recognizeMarker(id) { return function (marker) { return marker.id == id; } } - function close() { - if (closing) { - text += lineSep; - if (extraLinebreak) { text += lineSep; } - closing = extraLinebreak = false; - } - } - function addText(str) { - if (str) { - close(); - text += str; - } - } - function walk(node) { - if (node.nodeType == 1) { - var cmText = node.getAttribute("cm-text"); - if (cmText) { - addText(cmText); - 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(0))) - { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); } - return - } - if (node.getAttribute("contenteditable") == "false") { return } - var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName); - if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } - - if (isBlock) { close(); } - for (var i = 0; i < node.childNodes.length; i++) - { walk(node.childNodes[i]); } - - if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; } - if (isBlock) { closing = true; } - } else if (node.nodeType == 3) { - addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")); - } - } - for (;;) { - walk(from); - if (from == to) { break } - from = from.nextSibling; - extraLinebreak = false; - } - 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; - this.resetting = false; - }; - - TextareaInput.prototype.init = function (display) { - var this$1 = this; - - var input = this, cm = this.cm; - this.createField(display); - var te = this.textarea; - - display.wrapper.insertBefore(this.wrapper, 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 = +new Date; - 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 = +new Date; } - } - on(te, "cut", prepareCopyCut); - on(te, "copy", prepareCopyCut); - - on(display.scroller, "paste", function (e) { - if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } - if (!te.dispatchEvent) { - cm.state.pasteIncoming = +new Date; - input.focus(); - return - } - - // Pass the `paste` event to the textarea so it's handled by its event listener. - var event = new Event("paste"); - event.clipboardData = e.clipboardData; - te.dispatchEvent(event); - }); - - // 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.createField = function (_display) { - // Wraps and hides input textarea - this.wrapper = hiddenTextarea(); - // The semihidden textarea that is focused when the editor is - // focused, and receives input. - this.textarea = this.wrapper.firstChild; - }; - - TextareaInput.prototype.screenReaderLabelChanged = function (label) { - // Label for screenreaders, accessibility - if(label) { - this.textarea.setAttribute('aria-label', label); - } else { - this.textarea.removeAttribute('aria-label'); - } - }; - - 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 && typing) { return } - var cm = this.cm; - this.resetting = true; - 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; } - } - this.resetting = false; - }; - - 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.ownerDocument) != 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 || this.resetting || !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; - if (input.contextMenuPending) { input.contextMenuPending(); } - 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; - var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect(); - input.wrapper.style.cssText = "position: static"; - 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 = te.ownerDocument.defaultView.scrollY; } // Work around Chrome issue (#2712) - display.input.focus(); - if (webkit) { te.ownerDocument.defaultView.scrollTo(null, oldScrollY); } - display.input.reset(); - // Adds "Select all" to context menu in FF - if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } - input.contextMenuPending = rehide; - 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() { - if (input.contextMenuPending != rehide) { return } - 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"; - this.textarea.readOnly = !!val; - }; - - 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(textarea.ownerDocument); - 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 (!options.leaveSubmitMethodAlone && 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); - CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; - - // 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.65.10"; - - return CodeMirror; -}))); - - -/* -file https://github.com/codemirror/codemirror5/blob/5.65.10/addon/edit/matchbrackets.js -*/ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/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) { - var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && - (document.documentMode == null || document.documentMode < 8); - - var Pos = CodeMirror.Pos; - - var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"}; - - function bracketRegex(config) { - return config && config.bracketRegex || /[(){}[\]]/ - } - - function findMatchingBracket(cm, where, config) { - var line = cm.getLineHandle(where.line), pos = where.ch - 1; - var afterCursor = config && config.afterCursor - if (afterCursor == null) - afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className) - var re = bracketRegex(config) - - // A cursor is defined as between two characters, but in in vim command mode - // (i.e. not insert mode), the cursor is visually represented as a - // highlighted box on top of the 2nd character. Otherwise, we allow matches - // from before or after the cursor. - var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) || - re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)]; - if (!match) return null; - var dir = match.charAt(1) == ">" ? 1 : -1; - if (config && config.strict && (dir > 0) != (pos == where.ch)) return null; - var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); - - var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style, config); - if (found == null) return null; - return {from: Pos(where.line, pos), to: found && found.pos, - match: found && found.ch == match.charAt(0), forward: dir > 0}; - } - - // bracketRegex is used to specify which type of bracket to scan - // should be a regexp, e.g. /[[\]]/ - // - // Note: If "where" is on an open bracket, then this bracket is ignored. - // - // Returns false when no bracket was found, null when it reached - // maxScanLines and gave up - function scanForBracket(cm, where, dir, style, config) { - var maxScanLen = (config && config.maxScanLineLength) || 10000; - var maxScanLines = (config && config.maxScanLines) || 1000; - - var stack = []; - var re = bracketRegex(config) - var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) - : Math.max(cm.firstLine() - 1, where.line - maxScanLines); - for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { - var line = cm.getLine(lineNo); - if (!line) continue; - var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; - if (line.length > maxScanLen) continue; - if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); - for (; pos != end; pos += dir) { - var ch = line.charAt(pos); - if (re.test(ch) && (style === undefined || - (cm.getTokenTypeAt(Pos(lineNo, pos + 1)) || "") == (style || ""))) { - var match = matching[ch]; - if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch); - else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; - else stack.pop(); - } - } - } - return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; - } - - function matchBrackets(cm, autoclear, config) { - // Disable brace matching in long lines, since it'll cause hugely slow updates - var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000, - highlightNonMatching = config && config.highlightNonMatching; - var marks = [], ranges = cm.listSelections(); - for (var i = 0; i < ranges.length; i++) { - var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config); - if (match && (match.match || highlightNonMatching !== false) && cm.getLine(match.from.line).length <= maxHighlightLen) { - var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; - marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); - if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) - marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); - } - } - - if (marks.length) { - // Kludge to work around the IE bug from issue #1193, where text - // input stops going to the textarea whenever this fires. - if (ie_lt8 && cm.state.focused) cm.focus(); - - var clear = function() { - cm.operation(function() { - for (var i = 0; i < marks.length; i++) marks[i].clear(); - }); - }; - if (autoclear) setTimeout(clear, 800); - else return clear; - } - } - - function doMatchBrackets(cm) { - cm.operation(function() { - if (cm.state.matchBrackets.currentlyHighlighted) { - cm.state.matchBrackets.currentlyHighlighted(); - cm.state.matchBrackets.currentlyHighlighted = null; - } - cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); - }); - } - - function clearHighlighted(cm) { - if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) { - cm.state.matchBrackets.currentlyHighlighted(); - cm.state.matchBrackets.currentlyHighlighted = null; - } - } - - CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { - if (old && old != CodeMirror.Init) { - cm.off("cursorActivity", doMatchBrackets); - cm.off("focus", doMatchBrackets) - cm.off("blur", clearHighlighted) - clearHighlighted(cm); - } - if (val) { - cm.state.matchBrackets = typeof val == "object" ? val : {}; - cm.on("cursorActivity", doMatchBrackets); - cm.on("focus", doMatchBrackets) - cm.on("blur", clearHighlighted) - } - }); - - CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); - CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){ - // Backwards-compatibility kludge - if (oldConfig || typeof config == "boolean") { - if (!oldConfig) { - config = config ? {strict: true} : null - } else { - oldConfig.strict = config - config = oldConfig - } - } - return findMatchingBracket(this, pos, config) - }); - CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ - return scanForBracket(this, pos, dir, style, config); - }); -}); - - -/* -file https://github.com/codemirror/codemirror5/blob/5.65.10/addon/edit/trailingspace.js -*/ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/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) { - CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) { - if (prev == CodeMirror.Init) prev = false; - if (prev && !val) - cm.removeOverlay("trailingspace"); - else if (!prev && val) - cm.addOverlay({ - token: function(stream) { - for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {} - if (i > stream.pos) { stream.pos = i; return null; } - stream.pos = l; - return "trailingspace"; - }, - name: "trailingspace" - }); - }); -}); - - -/* -file https://github.com/codemirror/codemirror5/blob/5.65.10/addon/selection/active-line.js -*/ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/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 WRAP_CLASS = "CodeMirror-activeline"; - var BACK_CLASS = "CodeMirror-activeline-background"; - var GUTT_CLASS = "CodeMirror-activeline-gutter"; - - CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { - var prev = old == CodeMirror.Init ? false : old; - if (val == prev) return - if (prev) { - cm.off("beforeSelectionChange", selectionChange); - clearActiveLines(cm); - delete cm.state.activeLines; - } - if (val) { - cm.state.activeLines = []; - updateActiveLines(cm, cm.listSelections()); - cm.on("beforeSelectionChange", selectionChange); - } - }); - - function clearActiveLines(cm) { - for (var i = 0; i < cm.state.activeLines.length; i++) { - cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS); - cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS); - cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS); - } - } - - function sameArray(a, b) { - if (a.length != b.length) return false; - for (var i = 0; i < a.length; i++) - if (a[i] != b[i]) return false; - return true; - } - - function updateActiveLines(cm, ranges) { - var active = []; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - var option = cm.getOption("styleActiveLine"); - if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty()) - continue - var line = cm.getLineHandleVisualStart(range.head.line); - if (active[active.length - 1] != line) active.push(line); - } - if (sameArray(cm.state.activeLines, active)) return; - cm.operation(function() { - clearActiveLines(cm); - for (var i = 0; i < active.length; i++) { - cm.addLineClass(active[i], "wrap", WRAP_CLASS); - cm.addLineClass(active[i], "background", BACK_CLASS); - cm.addLineClass(active[i], "gutter", GUTT_CLASS); - } - cm.state.activeLines = active; - }); - } - - function selectionChange(cm, sel) { - updateActiveLines(cm, sel.ranges); - } -}); - - -/* -file https://github.com/codemirror/codemirror5/blob/5.65.10/mode/sql/sql.js -*/ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/5/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("sql", function(config, parserConfig) { - var client = parserConfig.client || {}, - atoms = parserConfig.atoms || {"false": true, "true": true, "null": true}, - builtin = parserConfig.builtin || set(defaultBuiltin), - keywords = parserConfig.keywords || set(sqlKeywords), - operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^\/]/, - support = parserConfig.support || {}, - hooks = parserConfig.hooks || {}, - dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true}, - backslashStringEscapes = parserConfig.backslashStringEscapes !== false, - brackets = parserConfig.brackets || /^[\{}\(\)\[\]]/, - punctuation = parserConfig.punctuation || /^[;.,:]/ - - function tokenBase(stream, state) { - var ch = stream.next(); - - // call hooks from the mime type - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - - if (support.hexNumber && - ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) - || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]*'/))) { - // hex - // ref: https://dev.mysql.com/doc/refman/8.0/en/hexadecimal-literals.html - return "number"; - } else if (support.binaryNumber && - (((ch == "b" || ch == "B") && stream.match(/^'[01]*'/)) - || (ch == "0" && stream.match(/^b[01]+/)))) { - // bitstring - // ref: https://dev.mysql.com/doc/refman/8.0/en/bit-value-literals.html - return "number"; - } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { - // numbers - // ref: https://dev.mysql.com/doc/refman/8.0/en/number-literals.html - stream.match(/^[0-9]*(\.[0-9]+)?([eE][-+]?[0-9]+)?/); - support.decimallessFloat && stream.match(/^\.(?!\.)/); - return "number"; - } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { - // placeholders - return "variable-3"; - } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { - // strings - // ref: https://dev.mysql.com/doc/refman/8.0/en/string-literals.html - state.tokenize = tokenLiteral(ch); - return state.tokenize(stream, state); - } else if ((((support.nCharCast && (ch == "n" || ch == "N")) - || (support.charsetCast && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) - && (stream.peek() == "'" || stream.peek() == '"'))) { - // charset casting: _utf8'str', N'str', n'str' - // ref: https://dev.mysql.com/doc/refman/8.0/en/string-literals.html - return "keyword"; - } else if (support.escapeConstant && (ch == "e" || ch == "E") - && (stream.peek() == "'" || (stream.peek() == '"' && support.doubleQuote))) { - // escape constant: E'str', e'str' - // ref: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE - state.tokenize = function(stream, state) { - return (state.tokenize = tokenLiteral(stream.next(), true))(stream, state); - } - return "keyword"; - } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) { - // 1-line comment - stream.skipToEnd(); - return "comment"; - } else if ((support.commentHash && ch == "#") - || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { - // 1-line comments - // ref: https://kb.askmonty.org/en/comment-syntax/ - stream.skipToEnd(); - return "comment"; - } else if (ch == "/" && stream.eat("*")) { - // multi-line comments - // ref: https://kb.askmonty.org/en/comment-syntax/ - state.tokenize = tokenComment(1); - return state.tokenize(stream, state); - } else if (ch == ".") { - // .1 for 0.1 - if (support.zerolessFloat && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) - return "number"; - if (stream.match(/^\.+/)) - return null - // .table_name (ODBC) - // // ref: https://dev.mysql.com/doc/refman/8.0/en/identifier-qualifiers.html - if (support.ODBCdotTable && stream.match(/^[\w\d_$#]+/)) - return "variable-2"; - } else if (operatorChars.test(ch)) { - // operators - stream.eatWhile(operatorChars); - return "operator"; - } else if (brackets.test(ch)) { - // brackets - return "bracket"; - } else if (punctuation.test(ch)) { - // punctuation - stream.eatWhile(punctuation); - return "punctuation"; - } else if (ch == '{' && - (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) { - // dates (weird ODBC syntax) - // ref: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-literals.html - return "number"; - } else { - stream.eatWhile(/^[_\w\d]/); - var word = stream.current().toLowerCase(); - // dates (standard SQL syntax) - // ref: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-literals.html - if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) - return "number"; - if (atoms.hasOwnProperty(word)) return "atom"; - if (builtin.hasOwnProperty(word)) return "type"; - if (keywords.hasOwnProperty(word)) return "keyword"; - if (client.hasOwnProperty(word)) return "builtin"; - return null; - } - } - - // 'string', with char specified in quote escaped by '\' - function tokenLiteral(quote, backslashEscapes) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = (backslashStringEscapes || backslashEscapes) && !escaped && ch == "\\"; - } - return "string"; - }; - } - function tokenComment(depth) { - return function(stream, state) { - var m = stream.match(/^.*?(\/\*|\*\/)/) - if (!m) stream.skipToEnd() - else if (m[1] == "/*") state.tokenize = tokenComment(depth + 1) - else if (depth > 1) state.tokenize = tokenComment(depth - 1) - else state.tokenize = tokenBase - return "comment" - } - } - - function pushContext(stream, state, type) { - state.context = { - prev: state.context, - indent: stream.indentation(), - col: stream.column(), - type: type - }; - } - - function popContext(state) { - state.indent = state.context.indent; - state.context = state.context.prev; - } - - return { - startState: function() { - return {tokenize: tokenBase, context: null}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.context && state.context.align == null) - state.context.align = false; - } - if (state.tokenize == tokenBase && stream.eatSpace()) return null; - - var style = state.tokenize(stream, state); - if (style == "comment") return style; - - if (state.context && state.context.align == null) - state.context.align = true; - - var tok = stream.current(); - if (tok == "(") - pushContext(stream, state, ")"); - else if (tok == "[") - pushContext(stream, state, "]"); - else if (state.context && state.context.type == tok) - popContext(state); - return style; - }, - - indent: function(state, textAfter) { - var cx = state.context; - if (!cx) return CodeMirror.Pass; - var closing = textAfter.charAt(0) == cx.type; - if (cx.align) return cx.col + (closing ? 0 : 1); - else return cx.indent + (closing ? 0 : config.indentUnit); - }, - - blockCommentStart: "/*", - blockCommentEnd: "*/", - lineComment: support.commentSlashSlash ? "//" : support.commentHash ? "#" : "--", - closeBrackets: "()[]{}''\"\"``", - config: parserConfig - }; -}); - - // `identifier` - function hookIdentifier(stream) { - // MySQL/MariaDB identifiers - // ref: https://dev.mysql.com/doc/refman/8.0/en/identifier-qualifiers.html - var ch; - while ((ch = stream.next()) != null) { - if (ch == "`" && !stream.eat("`")) return "variable-2"; - } - stream.backUp(stream.current().length - 1); - return stream.eatWhile(/\w/) ? "variable-2" : null; - } - - // "identifier" - function hookIdentifierDoublequote(stream) { - // Standard SQL /SQLite identifiers - // ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier - // ref: http://sqlite.org/lang_keywords.html - var ch; - while ((ch = stream.next()) != null) { - if (ch == "\"" && !stream.eat("\"")) return "variable-2"; - } - stream.backUp(stream.current().length - 1); - return stream.eatWhile(/\w/) ? "variable-2" : null; - } - - // variable token - function hookVar(stream) { - // variables - // @@prefix.varName @varName - // varName can be quoted with ` or ' or " - // ref: https://dev.mysql.com/doc/refman/8.0/en/user-variables.html - if (stream.eat("@")) { - stream.match('session.'); - stream.match('local.'); - stream.match('global.'); - } - - if (stream.eat("'")) { - stream.match(/^.*'/); - return "variable-2"; - } else if (stream.eat('"')) { - stream.match(/^.*"/); - return "variable-2"; - } else if (stream.eat("`")) { - stream.match(/^.*`/); - return "variable-2"; - } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { - return "variable-2"; - } - return null; - }; - - // short client keyword token - function hookClient(stream) { - // \N means NULL - // ref: https://dev.mysql.com/doc/refman/8.0/en/null-values.html - if (stream.eat("N")) { - return "atom"; - } - // \g, etc - // ref: https://dev.mysql.com/doc/refman/8.0/en/mysql-commands.html - return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; - } - - // these keywords are used by all SQL dialects (however, a mode can still overwrite it) - var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit "; - - // turn a space-separated list into an array - function set(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var defaultBuiltin = "bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric" - - // A generic SQL Mode. It's not a standard, it just tries to support what is generally supported - CodeMirror.defineMIME("text/x-sql", { - name: "sql", - keywords: set(sqlKeywords + "begin"), - builtin: set(defaultBuiltin), - atoms: set("false true null unknown"), - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") - }); - - CodeMirror.defineMIME("text/x-mssql", { - name: "sql", - client: set("$partition binary_checksum checksum connectionproperty context_info current_request_id error_line error_message error_number error_procedure error_severity error_state formatmessage get_filestream_transaction_context getansinull host_id host_name isnull isnumeric min_active_rowversion newid newsequentialid rowcount_big xact_state object_id"), - keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec go if use index holdlock nolock nowait paglock readcommitted readcommittedlock readpast readuncommitted repeatableread rowlock serializable snapshot tablock tablockx updlock with"), - builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "), - atoms: set("is not null like and or in left right between inner outer join all any some cross unpivot pivot exists"), - operatorChars: /^[*+\-%<>!=^\&|\/]/, - brackets: /^[\{}\(\)]/, - punctuation: /^[;.,:/]/, - backslashStringEscapes: false, - dateSQL: set("date datetimeoffset datetime2 smalldatetime datetime time"), - hooks: { - "@": hookVar - } - }); - - CodeMirror.defineMIME("text/x-mysql", { - name: "sql", - client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), - keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), - builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=&|^]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), - hooks: { - "@": hookVar, - "`": hookIdentifier, - "\\": hookClient - } - }); - - CodeMirror.defineMIME("text/x-mariadb", { - name: "sql", - client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), - keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group group_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), - builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=&|^]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), - hooks: { - "@": hookVar, - "`": hookIdentifier, - "\\": hookClient - } - }); - - // provided by the phpLiteAdmin project - phpliteadmin.org - CodeMirror.defineMIME("text/x-sqlite", { - name: "sql", - // commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd - client: set("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"), - // ref: http://sqlite.org/lang_keywords.html - keywords: set(sqlKeywords + "abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"), - // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. - builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"), - // ref: http://sqlite.org/syntax/literal-value.html - atoms: set("null current_date current_time current_timestamp"), - // ref: http://sqlite.org/lang_expr.html#binaryops - operatorChars: /^[*+\-%<>!=&|/~]/, - // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. - dateSQL: set("date time timestamp datetime"), - support: set("decimallessFloat zerolessFloat"), - identifierQuote: "\"", //ref: http://sqlite.org/lang_keywords.html - hooks: { - // bind-parameters ref:http://sqlite.org/lang_expr.html#varparam - "@": hookVar, - ":": hookVar, - "?": hookVar, - "$": hookVar, - // The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html - "\"": hookIdentifierDoublequote, - // there is also support for backticks, ref: http://sqlite.org/lang_keywords.html - "`": hookIdentifier - } - }); - - // the query language used by Apache Cassandra is called CQL, but this mime type - // is called Cassandra to avoid confusion with Contextual Query Language - CodeMirror.defineMIME("text/x-cassandra", { - name: "sql", - client: { }, - keywords: set("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"), - builtin: set("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"), - atoms: set("false true infinity NaN"), - operatorChars: /^[<>=]/, - dateSQL: { }, - support: set("commentSlashSlash decimallessFloat"), - hooks: { } - }); - - // this is based on Peter Raganitsch's 'plsql' mode - CodeMirror.defineMIME("text/x-plsql", { - name: "sql", - client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), - keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), - builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"), - operatorChars: /^[*\/+\-%<>!=~]/, - dateSQL: set("date time timestamp"), - support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") - }); - - // Created to support specific hive keywords - CodeMirror.defineMIME("text/x-hive", { - name: "sql", - keywords: set("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with admin authorization char compact compactions conf cube current current_date current_timestamp day decimal defined dependency directories elem_type exchange file following for grouping hour ignore inner interval jar less logical macro minute month more none noscan over owner partialscan preceding pretty principals protection reload rewrite role roles rollup rows second server sets skewed transactions truncate unbounded unset uri user values window year"), - builtin: set("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype key_type utctimestamp value_type varchar"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=]/, - dateSQL: set("date timestamp"), - support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") - }); - - CodeMirror.defineMIME("text/x-pgsql", { - name: "sql", - client: set("source"), - // For PostgreSQL - https://www.postgresql.org/docs/11/sql-keywords-appendix.html - // For pl/pgsql lang - https://github.com/postgres/postgres/blob/REL_11_2/src/pl/plpgsql/src/pl_scanner.c - keywords: set(sqlKeywords + "a abort abs absent absolute access according action ada add admin after aggregate alias all allocate also alter always analyse analyze and any are array array_agg array_max_cardinality as asc asensitive assert assertion assignment asymmetric at atomic attach attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli between bigint binary bit bit_length blob blocked bom boolean both breadth by c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain char char_length character character_length character_set_catalog character_set_name character_set_schema characteristics characters check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column column_name columns command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constant constraint constraint_catalog constraint_name constraint_schema constraints constructor contains content continue control conversion convert copy corr corresponding cost count covar_pop covar_samp create cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datatype date datetime_interval_code datetime_interval_precision day db deallocate debug dec decimal declare default defaults deferrable deferred defined definer degree delete delimiter delimiters dense_rank depends depth deref derived desc describe descriptor detach detail deterministic diagnostics dictionary disable discard disconnect dispatch distinct dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain double drop dump dynamic dynamic_function dynamic_function_code each element else elseif elsif empty enable encoding encrypted end end_frame end_partition endexec enforced enum equals errcode error escape event every except exception exclude excluding exclusive exec execute exists exit exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreach foreign fortran forward found frame_row free freeze from fs full function functions fusion g general generated get global go goto grant granted greatest group grouping groups handler having header hex hierarchy hint hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import in include including increment indent index indexes indicator info inherit inherits initially inline inner inout input insensitive insert instance instantiable instead int integer integrity intersect intersection interval into invoker is isnull isolation join k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like like_regex limit link listen ln load local localtime localtimestamp location locator lock locked log logged loop lower m map mapping match matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized not nothing notice notify notnull nowait nth_value ntile null nullable nullif nulls number numeric object occurrences_regex octet_length octets of off offset oids old on only open operator option options or order ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parallel parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password path percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding precision prepare prepared preserve primary print_strict_params prior privileges procedural procedure procedures program public publication query quote raise range rank read reads real reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result result_oid return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns reverse revoke right role rollback rollup routine routine_catalog routine_name routine_schema routines row row_count row_number rows rowtype rule savepoint scale schema schema_name schemas scope scope_catalog scope_name scope_schema scroll search second section security select selective self sensitive sequence sequences serializable server server_name session session_user set setof sets share show similar simple size skip slice smallint snapshot some source space specific specific_name specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable stacked standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time system_user t table table_name tables tablesample tablespace temp template temporary text then ties time timestamp timezone_hour timezone_minute to token top_level_count trailing transaction transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted union unique unknown unlink unlisten unlogged unnamed unnest until untyped update upper uri usage use_column use_variable user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of values var_pop var_samp varbinary varchar variable_conflict variadic varying verbose version versioning view views volatile warning when whenever where while whitespace width_bucket window with within without work wrapper write xml xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes zone"), - // https://www.postgresql.org/docs/11/datatype.html - builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), - atoms: set("false true null unknown"), - operatorChars: /^[*\/+\-%<>!=&|^\/#@?~]/, - backslashStringEscapes: false, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast escapeConstant") - }); - - // Google's SQL-like query language, GQL - CodeMirror.defineMIME("text/x-gql", { - name: "sql", - keywords: set("ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where"), - atoms: set("false true"), - builtin: set("blob datetime first key __key__ string integer double boolean null"), - operatorChars: /^[*+\-%<>!=]/ - }); - - // Greenplum - CodeMirror.defineMIME("text/x-gpsql", { - name: "sql", - client: set("source"), - //https://github.com/greenplum-db/gpdb/blob/master/src/include/parser/kwlist.h - keywords: set("abort absolute access action active add admin after aggregate all also alter always analyse analyze and any array as asc assertion assignment asymmetric at authorization backward before begin between bigint binary bit boolean both by cache called cascade cascaded case cast chain char character characteristics check checkpoint class close cluster coalesce codegen collate column comment commit committed concurrency concurrently configuration connection constraint constraints contains content continue conversion copy cost cpu_rate_limit create createdb createexttable createrole createuser cross csv cube current current_catalog current_date current_role current_schema current_time current_timestamp current_user cursor cycle data database day deallocate dec decimal declare decode default defaults deferrable deferred definer delete delimiter delimiters deny desc dictionary disable discard distinct distributed do document domain double drop dxl each else enable encoding encrypted end enum errors escape every except exchange exclude excluding exclusive execute exists explain extension external extract false family fetch fields filespace fill filter first float following for force foreign format forward freeze from full function global grant granted greatest group group_id grouping handler hash having header hold host hour identity if ignore ilike immediate immutable implicit in including inclusive increment index indexes inherit inherits initially inline inner inout input insensitive insert instead int integer intersect interval into invoker is isnull isolation join key language large last leading least left level like limit list listen load local localtime localtimestamp location lock log login mapping master match maxvalue median merge minute minvalue missing mode modifies modify month move name names national natural nchar new newline next no nocreatedb nocreateexttable nocreaterole nocreateuser noinherit nologin none noovercommit nosuperuser not nothing notify notnull nowait null nullif nulls numeric object of off offset oids old on only operator option options or order ordered others out outer over overcommit overlaps overlay owned owner parser partial partition partitions passing password percent percentile_cont percentile_disc placing plans position preceding precision prepare prepared preserve primary prior privileges procedural procedure protocol queue quote randomly range read readable reads real reassign recheck recursive ref references reindex reject relative release rename repeatable replace replica reset resource restart restrict returning returns revoke right role rollback rollup rootpartition row rows rule savepoint scatter schema scroll search second security segment select sequence serializable session session_user set setof sets share show similar simple smallint some split sql stable standalone start statement statistics stdin stdout storage strict strip subpartition subpartitions substring superuser symmetric sysid system table tablespace temp template temporary text then threshold ties time timestamp to trailing transaction treat trigger trim true truncate trusted type unbounded uncommitted unencrypted union unique unknown unlisten until update user using vacuum valid validation validator value values varchar variadic varying verbose version view volatile web when where whitespace window with within without work writable write xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot xmlserialize year yes zone"), - builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast") - }); - - // Spark SQL - CodeMirror.defineMIME("text/x-sparksql", { - name: "sql", - keywords: set("add after all alter analyze and anti archive array as asc at between bucket buckets by cache cascade case cast change clear cluster clustered codegen collection column columns comment commit compact compactions compute concatenate cost create cross cube current current_date current_timestamp database databases data dbproperties defined delete delimited deny desc describe dfs directories distinct distribute drop else end escaped except exchange exists explain export extended external false fields fileformat first following for format formatted from full function functions global grant group grouping having if ignore import in index indexes inner inpath inputformat insert intersect interval into is items join keys last lateral lazy left like limit lines list load local location lock locks logical macro map minus msck natural no not null nulls of on optimize option options or order out outer outputformat over overwrite partition partitioned partitions percent preceding principals purge range recordreader recordwriter recover reduce refresh regexp rename repair replace reset restrict revoke right rlike role roles rollback rollup row rows schema schemas select semi separated serde serdeproperties set sets show skewed sort sorted start statistics stored stratify struct table tables tablesample tblproperties temp temporary terminated then to touch transaction transactions transform true truncate unarchive unbounded uncache union unlock unset use using values view when where window with"), - builtin: set("abs acos acosh add_months aggregate and any approx_count_distinct approx_percentile array array_contains array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_repeat array_sort array_union arrays_overlap arrays_zip ascii asin asinh assert_true atan atan2 atanh avg base64 between bigint bin binary bit_and bit_count bit_get bit_length bit_or bit_xor bool_and bool_or boolean bround btrim cardinality case cast cbrt ceil ceiling char char_length character_length chr coalesce collect_list collect_set concat concat_ws conv corr cos cosh cot count count_if count_min_sketch covar_pop covar_samp crc32 cume_dist current_catalog current_database current_date current_timestamp current_timezone current_user date date_add date_format date_from_unix_date date_part date_sub date_trunc datediff day dayofmonth dayofweek dayofyear decimal decode degrees delimited dense_rank div double e element_at elt encode every exists exp explode explode_outer expm1 extract factorial filter find_in_set first first_value flatten float floor forall format_number format_string from_csv from_json from_unixtime from_utc_timestamp get_json_object getbit greatest grouping grouping_id hash hex hour hypot if ifnull in initcap inline inline_outer input_file_block_length input_file_block_start input_file_name inputformat instr int isnan isnotnull isnull java_method json_array_length json_object_keys json_tuple kurtosis lag last last_day last_value lcase lead least left length levenshtein like ln locate log log10 log1p log2 lower lpad ltrim make_date make_dt_interval make_interval make_timestamp make_ym_interval map map_concat map_entries map_filter map_from_arrays map_from_entries map_keys map_values map_zip_with max max_by md5 mean min min_by minute mod monotonically_increasing_id month months_between named_struct nanvl negative next_day not now nth_value ntile nullif nvl nvl2 octet_length or outputformat overlay parse_url percent_rank percentile percentile_approx pi pmod posexplode posexplode_outer position positive pow power printf quarter radians raise_error rand randn random rank rcfile reflect regexp regexp_extract regexp_extract_all regexp_like regexp_replace repeat replace reverse right rint rlike round row_number rpad rtrim schema_of_csv schema_of_json second sentences sequence sequencefile serde session_window sha sha1 sha2 shiftleft shiftright shiftrightunsigned shuffle sign signum sin sinh size skewness slice smallint some sort_array soundex space spark_partition_id split sqrt stack std stddev stddev_pop stddev_samp str_to_map string struct substr substring substring_index sum tan tanh textfile timestamp timestamp_micros timestamp_millis timestamp_seconds tinyint to_csv to_date to_json to_timestamp to_unix_timestamp to_utc_timestamp transform transform_keys transform_values translate trim trunc try_add try_divide typeof ucase unbase64 unhex uniontype unix_date unix_micros unix_millis unix_seconds unix_timestamp upper uuid var_pop var_samp variance version weekday weekofyear when width_bucket window xpath xpath_boolean xpath_double xpath_float xpath_int xpath_long xpath_number xpath_short xpath_string xxhash64 year zip_with"), - atoms: set("false true null"), - operatorChars: /^[*\/+\-%<>!=~&|^]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable doubleQuote zerolessFloat") - }); - - // Esper - CodeMirror.defineMIME("text/x-esper", { - name: "sql", - client: set("source"), - // http://www.espertech.com/esper/release-5.5.0/esper-reference/html/appendix_keywords.html - keywords: set("alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window"), - builtin: {}, - atoms: set("false true null"), - operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, - dateSQL: set("time"), - support: set("decimallessFloat zerolessFloat binaryNumber hexNumber") - }); - - // Trino (formerly known as Presto) - CodeMirror.defineMIME("text/x-trino", { - name: "sql", - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L859-L1129 - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/docs/src/main/sphinx/functions/list.rst - keywords: set("abs absent acos add admin after all all_match alter analyze and any any_match approx_distinct approx_most_frequent approx_percentile approx_set arbitrary array_agg array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_sort array_union arrays_overlap as asc asin at at_timezone atan atan2 authorization avg bar bernoulli beta_cdf between bing_tile bing_tile_at bing_tile_coordinates bing_tile_polygon bing_tile_quadkey bing_tile_zoom_level bing_tiles_around bit_count bitwise_and bitwise_and_agg bitwise_left_shift bitwise_not bitwise_or bitwise_or_agg bitwise_right_shift bitwise_right_shift_arithmetic bitwise_xor bool_and bool_or both by call cardinality cascade case cast catalogs cbrt ceil ceiling char2hexint checksum chr classify coalesce codepoint column columns combinations comment commit committed concat concat_ws conditional constraint contains contains_sequence convex_hull_agg copartition corr cos cosh cosine_similarity count count_if covar_pop covar_samp crc32 create cross cube cume_dist current current_catalog current_date current_groups current_path current_role current_schema current_time current_timestamp current_timezone current_user data date_add date_diff date_format date_parse date_trunc day day_of_month day_of_week day_of_year deallocate default define definer degrees delete dense_rank deny desc describe descriptor distinct distributed dow doy drop e element_at else empty empty_approx_set encoding end error escape evaluate_classifier_predictions every except excluding execute exists exp explain extract false features fetch filter final first first_value flatten floor following for format format_datetime format_number from from_base from_base32 from_base64 from_base64url from_big_endian_32 from_big_endian_64 from_encoded_polyline from_geojson_geometry from_hex from_ieee754_32 from_ieee754_64 from_iso8601_date from_iso8601_timestamp from_iso8601_timestamp_nanos from_unixtime from_unixtime_nanos from_utf8 full functions geometric_mean geometry_from_hadoop_shape geometry_invalid_reason geometry_nearest_points geometry_to_bing_tiles geometry_union geometry_union_agg grant granted grants graphviz great_circle_distance greatest group grouping groups hamming_distance hash_counts having histogram hmac_md5 hmac_sha1 hmac_sha256 hmac_sha512 hour human_readable_seconds if ignore in including index infinity initial inner input insert intersect intersection_cardinality into inverse_beta_cdf inverse_normal_cdf invoker io is is_finite is_infinite is_json_scalar is_nan isolation jaccard_index join json_array json_array_contains json_array_get json_array_length json_exists json_extract json_extract_scalar json_format json_object json_parse json_query json_size json_value keep key keys kurtosis lag last last_day_of_month last_value lateral lead leading learn_classifier learn_libsvm_classifier learn_libsvm_regressor learn_regressor least left length level levenshtein_distance like limit line_interpolate_point line_interpolate_points line_locate_point listagg ln local localtime localtimestamp log log10 log2 logical lower lpad ltrim luhn_check make_set_digest map_agg map_concat map_entries map_filter map_from_entries map_keys map_union map_values map_zip_with match match_recognize matched matches materialized max max_by md5 measures merge merge_set_digest millisecond min min_by minute mod month multimap_agg multimap_from_entries murmur3 nan natural next nfc nfd nfkc nfkd ngrams no none none_match normal_cdf normalize not now nth_value ntile null nullif nulls numeric_histogram object objectid_timestamp of offset omit on one only option or order ordinality outer output over overflow parse_data_size parse_datetime parse_duration partition partitions passing past path pattern per percent_rank permute pi position pow power preceding prepare privileges properties prune qdigest_agg quarter quotes radians rand random range rank read recursive reduce reduce_agg refresh regexp_count regexp_extract regexp_extract_all regexp_like regexp_position regexp_replace regexp_split regr_intercept regr_slope regress rename render repeat repeatable replace reset respect restrict returning reverse revoke rgb right role roles rollback rollup round row_number rows rpad rtrim running scalar schema schemas second security seek select sequence serializable session set sets sha1 sha256 sha512 show shuffle sign simplify_geometry sin skewness skip slice some soundex spatial_partitioning spatial_partitions split split_part split_to_map split_to_multimap spooky_hash_v2_32 spooky_hash_v2_64 sqrt st_area st_asbinary st_astext st_boundary st_buffer st_centroid st_contains st_convexhull st_coorddim st_crosses st_difference st_dimension st_disjoint st_distance st_endpoint st_envelope st_envelopeaspts st_equals st_exteriorring st_geometries st_geometryfromtext st_geometryn st_geometrytype st_geomfrombinary st_interiorringn st_interiorrings st_intersection st_intersects st_isclosed st_isempty st_isring st_issimple st_isvalid st_length st_linefromtext st_linestring st_multipoint st_numgeometries st_numinteriorring st_numpoints st_overlaps st_point st_pointn st_points st_polygon st_relate st_startpoint st_symdifference st_touches st_union st_within st_x st_xmax st_xmin st_y st_ymax st_ymin start starts_with stats stddev stddev_pop stddev_samp string strpos subset substr substring sum system table tables tablesample tan tanh tdigest_agg text then ties timestamp_objectid timezone_hour timezone_minute to to_base to_base32 to_base64 to_base64url to_big_endian_32 to_big_endian_64 to_char to_date to_encoded_polyline to_geojson_geometry to_geometry to_hex to_ieee754_32 to_ieee754_64 to_iso8601 to_milliseconds to_spherical_geography to_timestamp to_unixtime to_utf8 trailing transaction transform transform_keys transform_values translate trim trim_array true truncate try try_cast type typeof uescape unbounded uncommitted unconditional union unique unknown unmatched unnest update upper url_decode url_encode url_extract_fragment url_extract_host url_extract_parameter url_extract_path url_extract_port url_extract_protocol url_extract_query use user using utf16 utf32 utf8 validate value value_at_quantile values values_at_quantiles var_pop var_samp variance verbose version view week week_of_year when where width_bucket wilson_interval_lower wilson_interval_upper window with with_timezone within without word_stem work wrapper write xxhash64 year year_of_week yow zip zip_with"), - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/core/trino-main/src/main/java/io/trino/metadata/TypeRegistry.java#L131-L168 - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-ml/src/main/java/io/trino/plugin/ml/MLPlugin.java#L35 - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPlugin.java#L32 - // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoPlugin.java#L37 - builtin: set("array bigint bingtile boolean char codepoints color date decimal double function geometry hyperloglog int integer interval ipaddress joniregexp json json2016 jsonpath kdbtree likepattern map model objectid p4hyperloglog precision qdigest re2jregexp real regressor row setdigest smallint sphericalgeography tdigest time timestamp tinyint uuid varbinary varchar zone"), - atoms: set("false true null unknown"), - // https://trino.io/docs/current/functions/list.html#id1 - operatorChars: /^[[\]|<>=!\-+*/%]/, - dateSQL: set("date time timestamp zone"), - // hexNumber is necessary for VARBINARY literals, e.g. X'65683F' - // but it also enables 0xFF hex numbers, which Trino doesn't support. - support: set("ODBCdotTable decimallessFloat zerolessFloat hexNumber") - }); -}); - -/* - How Properties of Mime Types are used by SQL Mode - ================================================= - - keywords: - A list of keywords you want to be highlighted. - builtin: - A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). - operatorChars: - All characters that must be handled as operators. - client: - Commands parsed and executed by the client (not the server). - support: - A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. - * ODBCdotTable: .tableName - * zerolessFloat: .1 - * decimallessFloat: 1. - * hexNumber: X'01AF' X'01af' x'01AF' x'01af' 0x01AF 0x01af - * binaryNumber: b'01' B'01' 0b01 - * doubleQuote: "string" - * escapeConstant: E'' - * nCharCast: N'string' - * charsetCast: _utf8'string' - * commentHash: use # char for comments - * commentSlashSlash: use // for comments - * commentSpaceRequired: require a space after -- for comments - atoms: - Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: - UNKNOWN, INFINITY, UNDERFLOW, NaN... - dateSQL: - Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. -*/ - - -/* -file none -*/ -/*jslint-enable*/ diff --git a/branch-alpha/_sqlmath.napi6_darwin_arm64.node b/branch-alpha/_sqlmath.napi6_darwin_arm64.node new file mode 100755 index 0000000000..f8a4c0aabf Binary files /dev/null and b/branch-alpha/_sqlmath.napi6_darwin_arm64.node differ diff --git a/branch-alpha/_sqlmath.napi6_darwin_x64.node b/branch-alpha/_sqlmath.napi6_darwin_x64.node new file mode 100755 index 0000000000..f421dc5bc6 Binary files /dev/null and b/branch-alpha/_sqlmath.napi6_darwin_x64.node differ diff --git a/branch-alpha/_sqlmath.napi6_linux_x64.node b/branch-alpha/_sqlmath.napi6_linux_x64.node new file mode 100755 index 0000000000..e9254a75f2 Binary files /dev/null and b/branch-alpha/_sqlmath.napi6_linux_x64.node differ diff --git a/branch-alpha/_sqlmath.napi6_win32_x64.node b/branch-alpha/_sqlmath.napi6_win32_x64.node new file mode 100644 index 0000000000..13da41224a Binary files /dev/null and b/branch-alpha/_sqlmath.napi6_win32_x64.node differ diff --git a/branch-alpha/_sqlmath.shell_darwin_arm64 b/branch-alpha/_sqlmath.shell_darwin_arm64 new file mode 100755 index 0000000000..56ec094601 Binary files /dev/null and b/branch-alpha/_sqlmath.shell_darwin_arm64 differ diff --git a/branch-alpha/_sqlmath.shell_darwin_x64 b/branch-alpha/_sqlmath.shell_darwin_x64 new file mode 100755 index 0000000000..914587541c Binary files /dev/null and b/branch-alpha/_sqlmath.shell_darwin_x64 differ diff --git a/branch-alpha/_sqlmath.shell_linux_x64 b/branch-alpha/_sqlmath.shell_linux_x64 new file mode 100755 index 0000000000..70564126ac Binary files /dev/null and b/branch-alpha/_sqlmath.shell_linux_x64 differ diff --git a/branch-alpha/_sqlmath.shell_win32_x64.exe b/branch-alpha/_sqlmath.shell_win32_x64.exe new file mode 100644 index 0000000000..09127beaf8 Binary files /dev/null and b/branch-alpha/_sqlmath.shell_win32_x64.exe differ diff --git a/branch-alpha/asset_image_logo_128.png b/branch-alpha/asset_image_logo_128.png new file mode 100644 index 0000000000..ed3acf7dcb Binary files /dev/null and b/branch-alpha/asset_image_logo_128.png differ diff --git a/branch-alpha/asset_image_logo_256.png b/branch-alpha/asset_image_logo_256.png new file mode 100644 index 0000000000..5daa2e60d0 Binary files /dev/null and b/branch-alpha/asset_image_logo_256.png differ diff --git a/branch-alpha/asset_image_logo_32.png b/branch-alpha/asset_image_logo_32.png new file mode 100644 index 0000000000..16a8a66cfd Binary files /dev/null and b/branch-alpha/asset_image_logo_32.png differ diff --git a/branch-alpha/asset_image_logo_512.png b/branch-alpha/asset_image_logo_512.png new file mode 100644 index 0000000000..10f2efa2d8 Binary files /dev/null and b/branch-alpha/asset_image_logo_512.png differ diff --git a/branch-alpha/asset_image_logo_64.png b/branch-alpha/asset_image_logo_64.png new file mode 100644 index 0000000000..1ee583b0c0 Binary files /dev/null and b/branch-alpha/asset_image_logo_64.png differ diff --git a/branch-alpha/lib_lightgbm_darwin_arm64.dylib b/branch-alpha/lib_lightgbm_darwin_arm64.dylib new file mode 100755 index 0000000000..7c2e10cb01 Binary files /dev/null and b/branch-alpha/lib_lightgbm_darwin_arm64.dylib differ diff --git a/branch-alpha/lib_lightgbm_darwin_x64.dylib b/branch-alpha/lib_lightgbm_darwin_x64.dylib new file mode 100755 index 0000000000..b4dec919d9 Binary files /dev/null and b/branch-alpha/lib_lightgbm_darwin_x64.dylib differ diff --git a/branch-alpha/lib_lightgbm_linux_x64.so b/branch-alpha/lib_lightgbm_linux_x64.so new file mode 100755 index 0000000000..fb302dbea8 Binary files /dev/null and b/branch-alpha/lib_lightgbm_linux_x64.so differ diff --git a/branch-alpha/lib_lightgbm_win32_x64.dll b/branch-alpha/lib_lightgbm_win32_x64.dll new file mode 100644 index 0000000000..d75cb7562c Binary files /dev/null and b/branch-alpha/lib_lightgbm_win32_x64.dll differ diff --git a/branch-alpha/libomp_darwin_arm64.dylib b/branch-alpha/libomp_darwin_arm64.dylib new file mode 100644 index 0000000000..9b2920b267 Binary files /dev/null and b/branch-alpha/libomp_darwin_arm64.dylib differ diff --git a/branch-alpha/libomp_darwin_x64.dylib b/branch-alpha/libomp_darwin_x64.dylib new file mode 100644 index 0000000000..4b3e74a5d1 Binary files /dev/null and b/branch-alpha/libomp_darwin_x64.dylib differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_10_9_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_10_9_x86_64.whl new file mode 100644 index 0000000000..593765ceec Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_10_9_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..3917df41f7 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..ba0532e963 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp310-cp310-win_amd64.whl b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-win_amd64.whl new file mode 100644 index 0000000000..371d93a186 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp310-cp310-win_amd64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_10_9_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_10_9_x86_64.whl new file mode 100644 index 0000000000..b00450941c Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_10_9_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..907eb8265d Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..f2893f09d0 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp311-cp311-win_amd64.whl b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-win_amd64.whl new file mode 100644 index 0000000000..19f6941e57 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp311-cp311-win_amd64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_10_13_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_10_13_x86_64.whl new file mode 100644 index 0000000000..a5b0f255f3 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_10_13_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..3cdfd62d4a Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..3d64d18a72 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp312-cp312-win_amd64.whl b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-win_amd64.whl new file mode 100644 index 0000000000..080dea5e02 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp312-cp312-win_amd64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_10_13_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_10_13_x86_64.whl new file mode 100644 index 0000000000..6ea54f96dc Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_10_13_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..308fcf2521 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..482fdf3eb9 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp313-cp313-win_amd64.whl b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-win_amd64.whl new file mode 100644 index 0000000000..ae328fec06 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp313-cp313-win_amd64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_10_15_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_10_15_x86_64.whl new file mode 100644 index 0000000000..f85801343d Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_10_15_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..2551a65b9c Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..a6d82520c5 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314-win_amd64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-win_amd64.whl new file mode 100644 index 0000000000..46142fa0f8 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314-win_amd64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_10_15_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_10_15_x86_64.whl new file mode 100644 index 0000000000..7730abb816 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_10_15_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_11_0_arm64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_11_0_arm64.whl new file mode 100644 index 0000000000..f2f426346c Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-macosx_11_0_arm64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000..19c92c95dc Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/branch-alpha/sqlmath-2026.7.30.tar.gz b/branch-alpha/sqlmath-2026.7.30.tar.gz new file mode 100644 index 0000000000..76f3edaae9 Binary files /dev/null and b/branch-alpha/sqlmath-2026.7.30.tar.gz differ diff --git a/branch-alpha/sqlmath_wasm.js b/branch-alpha/sqlmath_wasm.js new file mode 100644 index 0000000000..0e655f8b31 --- /dev/null +++ b/branch-alpha/sqlmath_wasm.js @@ -0,0 +1,83 @@ +/*jslint-disable*/ +// Copyright (c) 2021 Kai Zhu +// SPDX-License-Identifier: MIT +// 2026-08-01T00:32:34+0000 +(function () { +"use strict"; + +var g;g||(g=typeof Module !== 'undefined' ? Module : {});var aa=Object.assign;function ba(a){return a&&a.constructor===ArrayBuffer} +(async function(){let a=h("dbFileLoadOrSave","number",["number","string","number"]),b=h("jsbatonGetErrmsg","string",["number"]),c=h("jsbatonGetString","string",["number","number"]),d=h("sqlite3_errmsg","string",["number"]),e;globalThis.onmessage=async function({data:f}){await e;f.batonPtr=0;try{a:{let G=f.FILENAME_DBTMP;var k=f.JSBATON_OFFSET_ALL;let Rb=f.JSBATON_OFFSET_BUFV,P=f.argList,Q=new Uint8Array(f.baton.buffer),C=0,A=P[4],Sb=0,ea="",W=f.funcname,Va=P[2];switch(W){case "_dbClose":case "_dbExec":case "_dbFileLoad":case "_dbNoop":case "_dbOpen":C= +ca(Q.byteLength);f.batonPtr=C;l.set(Q,C);switch(W){case "_dbClose":console.error(`_dbClose("${c(C,1)}")`);break;case "_dbFileLoad":Va||da(G,new Uint8Array(A));break;case "_dbOpen":console.error(`_dbOpen("${c(C,0)}")`)}fa(C);ea=b(C);Q.set(l.subarray(C,C+k));switch(!ea&&W){case "_dbExec":A=new BigInt64Array(Q.buffer,Rb);P[0]=l.slice(Number(A[0]),Number(A[0]+A[1])).buffer;ha(Number(A[0]));break;case "_dbFileLoad":if(Va){k=G;var m=m||0;var p=p||"binary";if("utf8"!==p&&"binary"!==p)throw Error('Invalid encoding type "'+ +p+'"');var q,t=n(k,m),v=r(k).size,H=new Uint8Array(v);ia(t,H,0,v,0);"utf8"===p?q=u(H,0):"binary"===p&&(q=H);ja(t);P[0]=q.buffer;ka(G)}break;case "_dbOpen":A&&(da(G,new Uint8Array(A)),A=Number(la(C,0)),(Sb=a(A,G,0))&&(ea=d(A)),ka(G))}postMessage({argList:P,baton:new DataView(Q.buffer),errmsg:ea,funcname:W,id:f.id},[Q.buffer,...P.filter(ba)]);var Tb=void 0;break a}throw Error(`invalid funcname "${W}"`);}await Tb}catch(G){postMessage({errmsg:G.stack,id:f.id})}finally{ha(f.batonPtr)}};e=new Promise(function(f){g.postRun= +f});await e;ma()})();var na=aa({},g),oa="./this.program",pa="object"===typeof window,w="function"===typeof importScripts,qa="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node,x="",ra,sa,ta,fs,ua,va; +if(qa)x=w?require("path").dirname(x)+"/":__dirname+"/",va=()=>{ua||(fs=require("fs"),ua=require("path"))},ra=function(a,b){va();a=ua.normalize(a);return fs.readFileSync(a,b?null:"utf8")},ta=a=>{a=ra(a,!0);a.buffer||(a=new Uint8Array(a));return a},sa=(a,b,c)=>{va();a=ua.normalize(a);fs.readFile(a,function(d,e){d?c(d):b(e.buffer)})},1{var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},w&&(ta=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),sa=(a,b,c)=>{var d=new XMLHttpRequest;d.open("GET",a,!0);d.responseType="arraybuffer"; +d.onload=()=>{200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var wa=g.print||console.log.bind(console),y=g.printErr||console.warn.bind(console);aa(g,na);na=null;g.thisProgram&&(oa=g.thisProgram);var z;g.wasmBinary&&(z=g.wasmBinary);var noExitRuntime=g.noExitRuntime||!0;"object"!==typeof WebAssembly&&B("no native wasm support detected");var xa,ya=!1; +function za(a,b,c,d){var e={string:function(q){var t=0;if(null!==q&&void 0!==q&&0!==q){var v=(q.length<<2)+1;t=Aa(v);D(q,l,t,v)}return t},array:function(q){var t=Aa(q.length);E.set(q,t);return t}};a=g["_"+a];var f=[],k=0;if(d)for(var m=0;m=d);)++c;if(16e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}function F(a){return a?u(l,a,void 0):""} +function D(a,b,c,d){if(!(0=k){var m=a.charCodeAt(++f);k=65536+((k&1023)<<10)|m&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-e} +function Ea(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Fa(a){var b=Ea(a)+1,c=Ga(b);c&&D(a,E,c,b);return c}var Ha,E,l,Ia,I,Ja; +function Ka(){var a=xa.buffer;Ha=a;g.HEAP8=E=new Int8Array(a);g.HEAP16=Ia=new Int16Array(a);g.HEAP32=I=new Int32Array(a);g.HEAPU8=l=new Uint8Array(a);g.HEAPU16=new Uint16Array(a);g.HEAPU32=new Uint32Array(a);g.HEAPF32=new Float32Array(a);g.HEAPF64=new Float64Array(a);g.HEAP64=Ja=new BigInt64Array(a);g.HEAPU64=new BigUint64Array(a)}var La,Ma=[],Na=[],Oa=[];function Pa(){var a=g.preRun.shift();Ma.unshift(a)}var J=0,Qa=null,Ra=null;g.preloadedImages={};g.preloadedAudios={}; +function B(a){if(g.onAbort)g.onAbort(a);a="Aborted("+a+")";y(a);ya=!0;throw new WebAssembly.RuntimeError(a+". Build with -s ASSERTIONS=1 for more info.");}function Sa(){return K.startsWith("data:application/octet-stream;base64,")}var K;K="sqlmath_wasm.wasm";if(!Sa()){var Ta=K;K=g.locateFile?g.locateFile(Ta,x):x+Ta}function Ua(){var a=K;try{if(a==K&&z)return new Uint8Array(z);if(ta)return ta(a);throw"both async and sync fetching of the wasm failed";}catch(b){B(b)}} +function Wa(){if(!z&&(pa||w)){if("function"===typeof fetch&&!K.startsWith("file://"))return fetch(K,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+K+"'";return a.arrayBuffer()}).catch(function(){return Ua()});if(sa)return new Promise(function(a,b){sa(K,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return Ua()})} +function Xa(a){for(;0=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.ga,a.ga=new Uint8Array(b),0=a.node.ia)return 0;a=Math.min(a.node.ia-e,d);if(8b)throw new M(28);return b},Aa:function(a,b,c){N.Ca(a.node,b+c);a.node.ia=Math.max(a.node.ia,b+c)},ta:function(a,b,c,d,e,f){if(0!==b)throw new M(28);if(32768!==(a.node.mode&61440))throw new M(43); +a=a.node.ga;if(f&2||a.buffer!==Ha){if(0{a=cb("/",a);if(!a)return{path:"",node:null};var c={Da:!0,za:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]); +if(8!!k),!1);var e=ob;c="/";for(d=0;d{for(var b;;){if(a===a.parent)return a=a.ma.Fa,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}},ub=(a,b)=>{for(var c=0,d=0;d>>0)%S.length},vb=a=>{var b=ub(a.parent.id,a.name);if(S[b]===a)S[b]=a.qa;else for(b=S[b];b;){if(b.qa===a){b.qa=a.qa;break}b=b.qa}},O=(a,b)=>{var c;if(c=(c=U(a,"x"))?c:a.ea.lookup?0:2)throw new M(c,a);for(c=S[ub(a.id,b)];c;c=c.qa){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.ea.lookup(a,b)},mb=(a,b,c,d)=>{a=new wb(a,b,c,d);b=ub(a.parent.id,a.name);a.qa=S[b];return S[b]=a},xb={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},yb=a=>{var b=["r","w","rw"][a& +3];a&512&&(b+="w");return b},U=(a,b)=>{if(rb)return 0;if(!b.includes("r")||a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0},zb=(a,b)=>{try{return O(a,b),20}catch(c){}return U(a,"wx")},Ab=(a,b,c)=>{try{var d=O(a,b)}catch(e){return e.ha}if(a=U(a,"wx"))return a;if(c){if(16384!==(d.mode&61440))return 54;if(d===d.parent||"/"===tb(d))return 10}else if(16384===(d.mode&61440))return 31;return 0},Bb=(a=0,b=4096)=>{for(;a<=b;a++)if(!R[a])return a; +throw new M(33);},Db=(a,b)=>{Cb||(Cb=function(){},Cb.prototype={});var c=new Cb,d;for(d in a)c[d]=a[d];a=c;b=Bb(b,void 0);a.fd=b;return R[b]=a},lb={open:a=>{a.fa=pb[a.node.rdev].fa;a.fa.open&&a.fa.open(a)},oa:()=>{throw new M(70);}},fb=(a,b)=>{pb[a]={fa:b}},Eb=(a,b)=>{var c="/"===b,d=!b;if(c&&ob)throw new M(10);if(!c&&!d){var e=T(b,{Da:!1});b=e.path;e=e.node;if(e.pa)throw new M(10);if(16384!==(e.mode&61440))throw new M(54);}b={type:a,Ua:{},Fa:b,Ma:[]};a=a.ma(b);a.ma=b;b.root=a;c?ob=a:e&&(e.pa=b,e.ma&& +e.ma.Ma.push(b))},Fb=(a,b,c)=>{var d=T(a,{parent:!0}).node;a=ab(a);if(!a||"."===a||".."===a)throw new M(28);var e=zb(d,a);if(e)throw new M(e);if(!d.ea.sa)throw new M(63);return d.ea.sa(d,a,b,c)},V=(a,b)=>Fb(a,(void 0!==b?b:511)&1023|16384,0),Gb=(a,b,c)=>{"undefined"===typeof c&&(c=b,b=438);Fb(a,b|8192,c)},Hb=(a,b)=>{if(!cb(a))throw new M(44);var c=T(b,{parent:!0}).node;if(!c)throw new M(44);b=ab(b);var d=zb(c,b);if(d)throw new M(d);if(!c.ea.symlink)throw new M(63);c.ea.symlink(c,b,a)},ka=a=>{var b= +T(a,{parent:!0}).node;if(!b)throw new M(44);a=ab(a);var c=O(b,a),d=Ab(b,a,!1);if(d)throw new M(d);if(!b.ea.unlink)throw new M(63);if(c.pa)throw new M(10);b.ea.unlink(b,a);vb(c)},sb=a=>{a=T(a).node;if(!a)throw new M(44);if(!a.ea.readlink)throw new M(28);return cb(tb(a.parent),a.ea.readlink(a))},r=(a,b)=>{a=T(a,{na:!b}).node;if(!a)throw new M(44);if(!a.ea.ka)throw new M(63);return a.ea.ka(a)},Ib=a=>r(a,!0),Jb=(a,b)=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{mode:b& +4095|a.mode&-4096,timestamp:Date.now()})},Kb=a=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{timestamp:Date.now()})},Lb=(a,b)=>{if(0>b)throw new M(28);a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);if(16384===(a.mode&61440))throw new M(31);if(32768!==(a.mode&61440))throw new M(28);var c=U(a,"w");if(c)throw new M(c);a.ea.ja(a,{size:b,timestamp:Date.now()})},n=(a,b,c,d)=>{if(""===a)throw new M(44);if("string"===typeof b){var e=xb[b];if("undefined"=== +typeof e)throw Error("Unknown file open mode: "+b);b=e}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var f=a;else{a=L(a);try{f=T(a,{na:!(b&131072)}).node}catch(k){}}e=!1;if(b&64)if(f){if(b&128)throw new M(20);}else f=Fb(a,c,0),e=!0;if(!f)throw new M(44);8192===(f.mode&61440)&&(b&=-513);if(b&65536&&16384!==(f.mode&61440))throw new M(54);if(!e&&(c=f?40960===(f.mode&61440)?32:16384===(f.mode&61440)&&("r"!==yb(b)||b&512)?31:U(f,yb(b)):44))throw new M(c);b&512&&Lb(f,0);b&=-131713; +d=Db({node:f,path:tb(f),flags:b,seekable:!0,position:0,fa:f.fa,Ra:[],error:!1},d);d.fa.open&&d.fa.open(d);!g.logReadFiles||b&1||(Mb||(Mb={}),a in Mb||(Mb[a]=1));return d},ja=a=>{if(null===a.fd)throw new M(8);a.xa&&(a.xa=null);try{a.fa.close&&a.fa.close(a)}catch(b){throw b;}finally{R[a.fd]=null}a.fd=null},Nb=(a,b,c)=>{if(null===a.fd)throw new M(8);if(!a.seekable||!a.fa.oa)throw new M(70);if(0!=c&&1!=c&&2!=c)throw new M(28);a.position=a.fa.oa(a,b,c);a.Ra=[]},ia=(a,b,c,d,e)=>{if(0>d||0>e)throw new M(28); +if(null===a.fd)throw new M(8);if(1===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.read)throw new M(28);var f="undefined"!==typeof e;if(!f)e=a.position;else if(!a.seekable)throw new M(70);b=a.fa.read(a,b,c,d,e);f||(a.position+=b);return b},Ob=(a,b,c,d,e)=>{var f=void 0;if(0>d||0>f)throw new M(28);if(null===a.fd)throw new M(8);if(0===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.write)throw new M(28);a.seekable&& +a.flags&1024&&Nb(a,0,2);var k="undefined"!==typeof f;if(!k)f=a.position;else if(!a.seekable)throw new M(70);b=a.fa.write(a,b,c,d,f,e);k||(a.position+=b);return b},da=(a,b)=>{var c={};c.flags=c.flags||577;a=n(a,c.flags,c.mode);if("string"===typeof b){var d=new Uint8Array(Ea(b)+1);b=D(b,d,0,d.length);Ob(a,d,0,b,c.Ia)}else if(ArrayBuffer.isView(b))Ob(a,b,0,b.byteLength,c.Ia);else throw Error("Unsupported data type");ja(a)},Pb=()=>{M||(M=function(a,b){this.node=b;this.Qa=function(c){this.ha=c};this.Qa(a); +this.message="FS error"},M.prototype=Error(),M.prototype.constructor=M,[44].forEach(a=>{nb[a]=new M(a);nb[a].stack=""}))},Qb,Ub=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},Wb=(a,b,c)=>{a=L("/dev/"+a);var d=Ub(!!b,!!c);Vb||(Vb=64);var e=Vb++<<8|0;fb(e,{open:f=>{f.seekable=!1},close:()=>{c&&c.buffer&&c.buffer.length&&c(10)},read:(f,k,m,p)=>{for(var q=0,t=0;t{for(var q=0;q>2]=d.dev;I[c+4>>2]=0;I[c+8>>2]=d.ino;I[c+12>>2]=d.mode;I[c+16>>2]=d.nlink;I[c+20>>2]=d.uid;I[c+24>>2]=d.gid;I[c+28>>2]=d.rdev;I[c+32>>2]=0;Ja[c+40>>3]=BigInt(d.size);I[c+48>>2]=4096;I[c+52>>2]=d.blocks;I[c+56>>2]=d.atime.getTime()/1E3|0;I[c+60>>2]=0;I[c+64>>2]=d.mtime.getTime()/1E3|0;I[c+68>>2]=0;I[c+72>>2]=d.ctime.getTime()/1E3|0;I[c+76>>2]=0;Ja[c+80>>3]=BigInt(d.ino);return 0}var Zb=void 0; +function Y(){Zb+=4;return I[Zb-4>>2]}function Z(a){a=R[a];if(!a)throw new M(8);return a}function $b(a,b,c){function d(p){return(p=p.toTimeString().match(/\(([A-Za-z ]+)\)$/))?p[1]:"GMT"}var e=(new Date).getFullYear(),f=new Date(e,0,1),k=new Date(e,6,1);e=f.getTimezoneOffset();var m=k.getTimezoneOffset();I[a>>2]=60*Math.max(e,m);I[b>>2]=Number(e!=m);a=d(f);b=d(k);a=Fa(a);b=Fa(b);m>2]=a,I[c+4>>2]=b):(I[c>>2]=b,I[c+4>>2]=a)}function ac(a,b,c){ac.Ha||(ac.Ha=!0,$b(a,b,c))}var bc; +bc=qa?()=>{var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:()=>performance.now();var cc={};function dc(){if(!ec){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:oa||"./this.program"},b;for(b in cc)void 0===cc[b]?delete a[b]:a[b]=cc[b];var c=[];for(b in a)c.push(b+"="+a[b]);ec=c}return ec}var ec; +function wb(a,b,c,d){a||(a=this);this.parent=a;this.ma=a.ma;this.pa=null;this.id=qb++;this.name=b;this.mode=c;this.ea={};this.fa={};this.rdev=d}Object.defineProperties(wb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}});Pb();S=Array(4096);Eb(N,"/");V("/tmp");V("/home");V("/home/web_user"); +(()=>{V("/dev");fb(259,{read:()=>0,write:(b,c,d,e)=>e});Gb("/dev/null",259);eb(1280,hb);eb(1536,ib);Gb("/dev/tty",1280);Gb("/dev/tty1",1536);var a=bb();Wb("random",a);Wb("urandom",a);V("/dev/shm");V("/dev/shm/tmp")})();(()=>{V("/proc");var a=V("/proc/self");V("/proc/self/fd");Eb({ma:()=>{var b=mb(a,"fd",16895,73);b.ea={lookup:(c,d)=>{var e=R[+d];if(!e)throw new M(8);c={parent:null,ma:{Fa:"fake"},ea:{readlink:()=>e.path}};return c.parent=c}};return b}},"/proc/self/fd")})(); +var hc={u:function(a,b){try{a=F(a);if(b&-8)var c=-28;else{var d=T(a,{na:!0}).node;d?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&U(d,a)?-2:0):c=-44}return c}catch(e){if("undefined"===typeof X||!(e instanceof M))throw e;return-e.ha}},K:function(a,b){try{return a=F(a),Jb(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},I:function(a){try{return a=F(a),Kb(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},i:function(a,b){try{var c= +R[a];if(!c)throw new M(8);Jb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},J:function(a){try{var b=R[a];if(!b)throw new M(8);Kb(b.node);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},a:function(a,b,c){Zb=c;try{var d=Z(a);switch(b){case 0:var e=Y();return 0>e?-28:n(d.path,d.flags,0,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=Y(),d.flags|=e,0;case 5:return e=Y(),Ia[e+0>>1]=2,0;case 6:case 7:return 0; +case 16:case 8:return-28;case 9:return I[fc()>>2]=28,-1;default:return-28}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},F:function(a,b){try{var c=Z(a);return Yb(r,c.path,b)}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},B:function(a,b,c,d){try{b=F(b);var e=d&256;d&=4096;var f=b;if("/"===f[0])b=f;else{if(-100===a)var k="/";else{var m=R[a];if(!m)throw new M(8);k=m.path}if(0==f.length){if(!d)throw new M(44);b=k}else b=L(k+"/"+f)}return Yb(e? +Ib:r,b,c)}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},z:function(a,b){try{var c=R[a];if(!c)throw new M(8);if(0===(c.flags&2097155))throw new M(28);Lb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},y:function(a,b){try{if(0===b)return-28;if(b>2]=0;case 21520:return d.tty?-28:-59;case 21531:a=e=Y();if(!d.fa.Ja)throw new M(59);return d.fa.Ja(d,b,a);case 21523:return d.tty?0:-59;case 21524:return d.tty?0:-59;default:B("bad ioctl syscall "+b)}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},C:function(a,b){try{return a=F(a),Yb(Ib,a,b)}catch(c){if("undefined"===typeof X|| +!(c instanceof M))throw c;return-c.ha}},s:function(a,b){try{return a=F(a),a=L(a),"/"===a[a.length-1]&&(a=a.substr(0,a.length-1)),V(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},r:function(a,b,c,d,e,f){try{a:{f<<=12;var k=!1;if(0!==(d&16)&&0!==a%65536)var m=-28;else{if(0!==(d&32)){var p=jb(b);if(!p){m=-48;break a}k=!0}else{var q=R[e];if(!q){m=-8;break a}var t=f;if(0!==(c&2)&&0===(d&2)&&2!==(q.flags&2097155))throw new M(2);if(1===(q.flags&2097155))throw new M(2); +if(!q.fa.ta)throw new M(43);var v=q.fa.ta(q,a,b,t,c,d);p=v.Oa;k=v.va}Xb[p]={La:p,Ka:b,va:k,fd:e,Na:c,flags:d,offset:f};m=p}}return m}catch(H){if("undefined"===typeof X||!(H instanceof M))throw H;return-H.ha}},q:function(a,b){try{var c=Xb[a];if(0!==b&&c){if(b===c.Ka){var d=R[c.fd];if(d&&c.Na&2){var e=c.flags,f=c.offset,k=l.slice(a,a+b);d&&d.fa.ua&&d.fa.ua(d,k,f,b,e)}Xb[a]=null;c.va&&gc(c.La)}var m=0}else m=-28;return m}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},h:function(a, +b,c){Zb=c;try{var d=F(a),e=c?Y():0;return n(d,b,e).fd}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},p:function(a,b,c){try{a=F(a);if(0>=c)var d=-28;else{var e=sb(a),f=Math.min(c,Ea(e)),k=E[b+f];D(e,l,b,c+1);E[b+f]=k;d=f}return d}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return-m.ha}},o:function(a){try{a=F(a);var b=T(a,{parent:!0}).node,c=ab(a),d=O(b,c),e=Ab(b,c,!0);if(e)throw new M(e);if(!b.ea.rmdir)throw new M(63);if(d.pa)throw new M(10);b.ea.rmdir(b, +c);vb(d);return 0}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},D:function(a,b){try{return a=F(a),Yb(r,a,b)}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},n:function(a){try{return a=F(a),ka(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},d:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")},l:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}, +j:function(a,b){a=new Date(1E3*I[a>>2]);I[b>>2]=a.getSeconds();I[b+4>>2]=a.getMinutes();I[b+8>>2]=a.getHours();I[b+12>>2]=a.getDate();I[b+16>>2]=a.getMonth();I[b+20>>2]=a.getFullYear()-1900;I[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);I[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;I[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();c=c.getTimezoneOffset();I[b+32>>2]=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0},k:ac,e:bc,G:function(a,b,c){l.copyWithin(a, +b,b+c)},c:function(a){var b=l.length;a>>>=0;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a,d);0>>16);Ka();var e=1;break a}catch(f){}e=void 0}if(e)return!0}return!1},w:function(a,b){var c=0;dc().forEach(function(d,e){var f=b+c;e=I[a+4*e>>2]=f;for(f=0;f>0]=d.charCodeAt(f);E[e>>0]=0;c+=d.length+1});return 0},x:function(a,b){var c=dc();I[a>> +2]=c.length;var d=0;c.forEach(function(e){d+=e.length+1});I[b>>2]=d;return 0},b:function(a){try{var b=Z(a);ja(b);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},m:function(a,b){try{var c=Z(a);E[b>>0]=c.tty?2:16384===(c.mode&61440)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return d.ha}},g:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],m=ia(e,E,I[b+8*f>>2],k,void 0);if(0>m){var p= +-1;break a}a+=m;if(m>2]=p;return 0}catch(q){if("undefined"===typeof X||!(q instanceof M))throw q;return q.ha}},t:function(a,b,c,d){try{var e=Number(b&BigInt(4294967295))|0,f=Number(b>>BigInt(32))|0,k=Z(a);a=4294967296*f+(e>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Nb(k,a,c);Ja[d>>3]=BigInt(k.position);k.xa&&0===a&&0===c&&(k.xa=null);return 0}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return m.ha}},A:function(a){try{var b=Z(a);return b.fa&&b.fa.fsync? +-b.fa.fsync(b):0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},f:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],I[b+(8*f+4)>>2]);if(0>k){var m=-1;break a}a+=k}m=a}I[d>>2]=m;return 0}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return p.ha}},E:function(a){var b=Date.now();I[a>>2]=b/1E3|0;I[a+4>>2]=b%1E3*1E3|0;return 0},L:function(a){var b=Date.now()/1E3|0;a&&(I[a>>2]=b);return b},M:function(a,b){if(b){var c=b+8;b=1E3*I[c>> +2];b+=I[c+4>>2]/1E3}else b=Date.now();a=F(a);try{var d=T(a,{na:!0}).node;d.ea.ja(d,{timestamp:Math.max(b,b)});var e=0}catch(f){if(!(f instanceof M)){b:{e=Error();if(!e.stack){try{throw Error();}catch(k){e=k}if(!e.stack){e="(no stack trace available)";break b}}e=e.stack.toString()}g.extraStackTrace&&(e+="\n"+g.extraStackTrace());e=Ya(e);throw f+" : "+e;}e=f.ha;I[fc()>>2]=e;e=-1}return e}}; +(function(){function a(e){g.asm=e.exports;xa=g.asm.N;Ka();La=g.asm.$;Na.unshift(g.asm.O);J--;g.monitorRunDependencies&&g.monitorRunDependencies(J);0==J&&(null!==Qa&&(clearInterval(Qa),Qa=null),Ra&&(e=Ra,Ra=null,e()))}function b(e){a(e.instance)}function c(e){return Wa().then(function(f){return WebAssembly.instantiate(f,d)}).then(function(f){return f}).then(e,function(f){y("failed to asynchronously prepare wasm: "+f);B(f)})}var d={a:hc};J++;g.monitorRunDependencies&&g.monitorRunDependencies(J);if(g.instantiateWasm)try{return g.instantiateWasm(d, +a)}catch(e){return y("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||Sa()||K.startsWith("file://")||"function"!==typeof fetch?c(b):fetch(K,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(f){y("wasm streaming compile failed: "+f);y("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +g.___wasm_call_ctors=function(){return(g.___wasm_call_ctors=g.asm.O).apply(null,arguments)};var fa=g._dbCall=function(){return(fa=g._dbCall=g.asm.P).apply(null,arguments)};g._jsbatonGetString=function(){return(g._jsbatonGetString=g.asm.Q).apply(null,arguments)};var ca=g._sqlite3_malloc=function(){return(ca=g._sqlite3_malloc=g.asm.R).apply(null,arguments)},ha=g._sqlite3_free=function(){return(ha=g._sqlite3_free=g.asm.S).apply(null,arguments)}; +g._dbFileLoadOrSave=function(){return(g._dbFileLoadOrSave=g.asm.T).apply(null,arguments)};var la=g._jsbatonGetInt64=function(){return(la=g._jsbatonGetInt64=g.asm.U).apply(null,arguments)};g._sqlite3_errmsg=function(){return(g._sqlite3_errmsg=g.asm.V).apply(null,arguments)};g._jsbatonGetErrmsg=function(){return(g._jsbatonGetErrmsg=g.asm.W).apply(null,arguments)}; +var ma=g._sqlite3_initialize=function(){return(ma=g._sqlite3_initialize=g.asm.X).apply(null,arguments)},fc=g.___errno_location=function(){return(fc=g.___errno_location=g.asm.Y).apply(null,arguments)},Ga=g._malloc=function(){return(Ga=g._malloc=g.asm.Z).apply(null,arguments)},gc=g._free=function(){return(gc=g._free=g.asm._).apply(null,arguments)},kb=g._memalign=function(){return(kb=g._memalign=g.asm.aa).apply(null,arguments)},Ba=g.stackSave=function(){return(Ba=g.stackSave=g.asm.ba).apply(null,arguments)}, +Ca=g.stackRestore=function(){return(Ca=g.stackRestore=g.asm.ca).apply(null,arguments)},Aa=g.stackAlloc=function(){return(Aa=g.stackAlloc=g.asm.da).apply(null,arguments)};g.cwrap=h;var ic;Ra=function jc(){ic||kc();ic||(Ra=jc)}; +function kc(){function a(){if(!ic&&(ic=!0,g.calledRun=!0,!ya)){g.noFSInit||Qb||(Qb=!0,Pb(),g.stdin=g.stdin,g.stdout=g.stdout,g.stderr=g.stderr,g.stdin?Wb("stdin",g.stdin):Hb("/dev/tty","/dev/stdin"),g.stdout?Wb("stdout",null,g.stdout):Hb("/dev/tty","/dev/stdout"),g.stderr?Wb("stderr",null,g.stderr):Hb("/dev/tty1","/dev/stderr"),n("/dev/stdin",0),n("/dev/stdout",1),n("/dev/stderr",1));rb=!1;Xa(Na);if(g.onRuntimeInitialized)g.onRuntimeInitialized();if(g.postRun)for("function"==typeof g.postRun&&(g.postRun= +[g.postRun]);g.postRun.length;){var b=g.postRun.shift();Oa.unshift(b)}Xa(Oa)}}if(!(0{ua||(fs=require("fs"),ua=require("path"))},ra=function(a,b){va();a=ua.normalize(a);return fs.readFileSync(a,b?null:"utf8")},ta=a=>{a=ra(a,!0);a.buffer||(a=new Uint8Array(a));return a},sa=(a,b,c)=>{va();a=ua.normalize(a);fs.readFile(a,function(d,e){d?c(d):b(e.buffer)})},1{var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},w&&(ta=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),sa=(a,b,c)=>{var d=new XMLHttpRequest;d.open("GET",a,!0);d.responseType="arraybuffer"; +d.onload=()=>{200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var wa=g.print||console.log.bind(console),y=g.printErr||console.warn.bind(console);aa(g,na);na=null;g.thisProgram&&(oa=g.thisProgram);var z;g.wasmBinary&&(z=g.wasmBinary);var noExitRuntime=g.noExitRuntime||!0;"object"!==typeof WebAssembly&&B("no native wasm support detected");var xa,ya=!1; +function za(a,b,c,d){var e={string:function(q){var t=0;if(null!==q&&void 0!==q&&0!==q){var v=(q.length<<2)+1;t=Aa(v);D(q,l,t,v)}return t},array:function(q){var t=Aa(q.length);E.set(q,t);return t}};a=g["_"+a];var f=[],k=0;if(d)for(var m=0;m=d);)++c;if(16e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}function F(a){return a?u(l,a,void 0):""} +function D(a,b,c,d){if(!(0=k){var m=a.charCodeAt(++f);k=65536+((k&1023)<<10)|m&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-e} +function Ea(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Fa(a){var b=Ea(a)+1,c=Ga(b);c&&D(a,E,c,b);return c}var Ha,E,l,Ia,I,Ja; +function Ka(){var a=xa.buffer;Ha=a;g.HEAP8=E=new Int8Array(a);g.HEAP16=Ia=new Int16Array(a);g.HEAP32=I=new Int32Array(a);g.HEAPU8=l=new Uint8Array(a);g.HEAPU16=new Uint16Array(a);g.HEAPU32=new Uint32Array(a);g.HEAPF32=new Float32Array(a);g.HEAPF64=new Float64Array(a);g.HEAP64=Ja=new BigInt64Array(a);g.HEAPU64=new BigUint64Array(a)}var La,Ma=[],Na=[],Oa=[];function Pa(){var a=g.preRun.shift();Ma.unshift(a)}var J=0,Qa=null,Ra=null;g.preloadedImages={};g.preloadedAudios={}; +function B(a){if(g.onAbort)g.onAbort(a);a="Aborted("+a+")";y(a);ya=!0;throw new WebAssembly.RuntimeError(a+". Build with -s ASSERTIONS=1 for more info.");}function Sa(){return K.startsWith("data:application/octet-stream;base64,")}var K;K="sqlmath_wasm.wasm";if(!Sa()){var Ta=K;K=g.locateFile?g.locateFile(Ta,x):x+Ta}function Ua(){var a=K;try{if(a==K&&z)return new Uint8Array(z);if(ta)return ta(a);throw"both async and sync fetching of the wasm failed";}catch(b){B(b)}} +function Wa(){if(!z&&(pa||w)){if("function"===typeof fetch&&!K.startsWith("file://"))return fetch(K,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+K+"'";return a.arrayBuffer()}).catch(function(){return Ua()});if(sa)return new Promise(function(a,b){sa(K,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return Ua()})} +function Xa(a){for(;0=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.ga,a.ga=new Uint8Array(b),0=a.node.ia)return 0;a=Math.min(a.node.ia-e,d);if(8b)throw new M(28);return b},Aa:function(a,b,c){N.Ca(a.node,b+c);a.node.ia=Math.max(a.node.ia,b+c)},ta:function(a,b,c,d,e,f){if(0!==b)throw new M(28);if(32768!==(a.node.mode&61440))throw new M(43); +a=a.node.ga;if(f&2||a.buffer!==Ha){if(0{a=cb("/",a);if(!a)return{path:"",node:null};var c={Da:!0,za:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]); +if(8!!k),!1);var e=ob;c="/";for(d=0;d{for(var b;;){if(a===a.parent)return a=a.ma.Fa,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}},ub=(a,b)=>{for(var c=0,d=0;d>>0)%S.length},vb=a=>{var b=ub(a.parent.id,a.name);if(S[b]===a)S[b]=a.qa;else for(b=S[b];b;){if(b.qa===a){b.qa=a.qa;break}b=b.qa}},O=(a,b)=>{var c;if(c=(c=U(a,"x"))?c:a.ea.lookup?0:2)throw new M(c,a);for(c=S[ub(a.id,b)];c;c=c.qa){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.ea.lookup(a,b)},mb=(a,b,c,d)=>{a=new wb(a,b,c,d);b=ub(a.parent.id,a.name);a.qa=S[b];return S[b]=a},xb={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},yb=a=>{var b=["r","w","rw"][a& +3];a&512&&(b+="w");return b},U=(a,b)=>{if(rb)return 0;if(!b.includes("r")||a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0},zb=(a,b)=>{try{return O(a,b),20}catch(c){}return U(a,"wx")},Ab=(a,b,c)=>{try{var d=O(a,b)}catch(e){return e.ha}if(a=U(a,"wx"))return a;if(c){if(16384!==(d.mode&61440))return 54;if(d===d.parent||"/"===tb(d))return 10}else if(16384===(d.mode&61440))return 31;return 0},Bb=(a=0,b=4096)=>{for(;a<=b;a++)if(!R[a])return a; +throw new M(33);},Db=(a,b)=>{Cb||(Cb=function(){},Cb.prototype={});var c=new Cb,d;for(d in a)c[d]=a[d];a=c;b=Bb(b,void 0);a.fd=b;return R[b]=a},lb={open:a=>{a.fa=pb[a.node.rdev].fa;a.fa.open&&a.fa.open(a)},oa:()=>{throw new M(70);}},fb=(a,b)=>{pb[a]={fa:b}},Eb=(a,b)=>{var c="/"===b,d=!b;if(c&&ob)throw new M(10);if(!c&&!d){var e=T(b,{Da:!1});b=e.path;e=e.node;if(e.pa)throw new M(10);if(16384!==(e.mode&61440))throw new M(54);}b={type:a,Ua:{},Fa:b,Ma:[]};a=a.ma(b);a.ma=b;b.root=a;c?ob=a:e&&(e.pa=b,e.ma&& +e.ma.Ma.push(b))},Fb=(a,b,c)=>{var d=T(a,{parent:!0}).node;a=ab(a);if(!a||"."===a||".."===a)throw new M(28);var e=zb(d,a);if(e)throw new M(e);if(!d.ea.sa)throw new M(63);return d.ea.sa(d,a,b,c)},V=(a,b)=>Fb(a,(void 0!==b?b:511)&1023|16384,0),Gb=(a,b,c)=>{"undefined"===typeof c&&(c=b,b=438);Fb(a,b|8192,c)},Hb=(a,b)=>{if(!cb(a))throw new M(44);var c=T(b,{parent:!0}).node;if(!c)throw new M(44);b=ab(b);var d=zb(c,b);if(d)throw new M(d);if(!c.ea.symlink)throw new M(63);c.ea.symlink(c,b,a)},ka=a=>{var b= +T(a,{parent:!0}).node;if(!b)throw new M(44);a=ab(a);var c=O(b,a),d=Ab(b,a,!1);if(d)throw new M(d);if(!b.ea.unlink)throw new M(63);if(c.pa)throw new M(10);b.ea.unlink(b,a);vb(c)},sb=a=>{a=T(a).node;if(!a)throw new M(44);if(!a.ea.readlink)throw new M(28);return cb(tb(a.parent),a.ea.readlink(a))},r=(a,b)=>{a=T(a,{na:!b}).node;if(!a)throw new M(44);if(!a.ea.ka)throw new M(63);return a.ea.ka(a)},Ib=a=>r(a,!0),Jb=(a,b)=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{mode:b& +4095|a.mode&-4096,timestamp:Date.now()})},Kb=a=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{timestamp:Date.now()})},Lb=(a,b)=>{if(0>b)throw new M(28);a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);if(16384===(a.mode&61440))throw new M(31);if(32768!==(a.mode&61440))throw new M(28);var c=U(a,"w");if(c)throw new M(c);a.ea.ja(a,{size:b,timestamp:Date.now()})},n=(a,b,c,d)=>{if(""===a)throw new M(44);if("string"===typeof b){var e=xb[b];if("undefined"=== +typeof e)throw Error("Unknown file open mode: "+b);b=e}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var f=a;else{a=L(a);try{f=T(a,{na:!(b&131072)}).node}catch(k){}}e=!1;if(b&64)if(f){if(b&128)throw new M(20);}else f=Fb(a,c,0),e=!0;if(!f)throw new M(44);8192===(f.mode&61440)&&(b&=-513);if(b&65536&&16384!==(f.mode&61440))throw new M(54);if(!e&&(c=f?40960===(f.mode&61440)?32:16384===(f.mode&61440)&&("r"!==yb(b)||b&512)?31:U(f,yb(b)):44))throw new M(c);b&512&&Lb(f,0);b&=-131713; +d=Db({node:f,path:tb(f),flags:b,seekable:!0,position:0,fa:f.fa,Ra:[],error:!1},d);d.fa.open&&d.fa.open(d);!g.logReadFiles||b&1||(Mb||(Mb={}),a in Mb||(Mb[a]=1));return d},ja=a=>{if(null===a.fd)throw new M(8);a.xa&&(a.xa=null);try{a.fa.close&&a.fa.close(a)}catch(b){throw b;}finally{R[a.fd]=null}a.fd=null},Nb=(a,b,c)=>{if(null===a.fd)throw new M(8);if(!a.seekable||!a.fa.oa)throw new M(70);if(0!=c&&1!=c&&2!=c)throw new M(28);a.position=a.fa.oa(a,b,c);a.Ra=[]},ia=(a,b,c,d,e)=>{if(0>d||0>e)throw new M(28); +if(null===a.fd)throw new M(8);if(1===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.read)throw new M(28);var f="undefined"!==typeof e;if(!f)e=a.position;else if(!a.seekable)throw new M(70);b=a.fa.read(a,b,c,d,e);f||(a.position+=b);return b},Ob=(a,b,c,d,e)=>{var f=void 0;if(0>d||0>f)throw new M(28);if(null===a.fd)throw new M(8);if(0===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.write)throw new M(28);a.seekable&& +a.flags&1024&&Nb(a,0,2);var k="undefined"!==typeof f;if(!k)f=a.position;else if(!a.seekable)throw new M(70);b=a.fa.write(a,b,c,d,f,e);k||(a.position+=b);return b},da=(a,b)=>{var c={};c.flags=c.flags||577;a=n(a,c.flags,c.mode);if("string"===typeof b){var d=new Uint8Array(Ea(b)+1);b=D(b,d,0,d.length);Ob(a,d,0,b,c.Ia)}else if(ArrayBuffer.isView(b))Ob(a,b,0,b.byteLength,c.Ia);else throw Error("Unsupported data type");ja(a)},Pb=()=>{M||(M=function(a,b){this.node=b;this.Qa=function(c){this.ha=c};this.Qa(a); +this.message="FS error"},M.prototype=Error(),M.prototype.constructor=M,[44].forEach(a=>{nb[a]=new M(a);nb[a].stack=""}))},Qb,Ub=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},Wb=(a,b,c)=>{a=L("/dev/"+a);var d=Ub(!!b,!!c);Vb||(Vb=64);var e=Vb++<<8|0;fb(e,{open:f=>{f.seekable=!1},close:()=>{c&&c.buffer&&c.buffer.length&&c(10)},read:(f,k,m,p)=>{for(var q=0,t=0;t{for(var q=0;q>2]=d.dev;I[c+4>>2]=0;I[c+8>>2]=d.ino;I[c+12>>2]=d.mode;I[c+16>>2]=d.nlink;I[c+20>>2]=d.uid;I[c+24>>2]=d.gid;I[c+28>>2]=d.rdev;I[c+32>>2]=0;Ja[c+40>>3]=BigInt(d.size);I[c+48>>2]=4096;I[c+52>>2]=d.blocks;I[c+56>>2]=d.atime.getTime()/1E3|0;I[c+60>>2]=0;I[c+64>>2]=d.mtime.getTime()/1E3|0;I[c+68>>2]=0;I[c+72>>2]=d.ctime.getTime()/1E3|0;I[c+76>>2]=0;Ja[c+80>>3]=BigInt(d.ino);return 0}var Zb=void 0; +function Y(){Zb+=4;return I[Zb-4>>2]}function Z(a){a=R[a];if(!a)throw new M(8);return a}function $b(a,b,c){function d(p){return(p=p.toTimeString().match(/\(([A-Za-z ]+)\)$/))?p[1]:"GMT"}var e=(new Date).getFullYear(),f=new Date(e,0,1),k=new Date(e,6,1);e=f.getTimezoneOffset();var m=k.getTimezoneOffset();I[a>>2]=60*Math.max(e,m);I[b>>2]=Number(e!=m);a=d(f);b=d(k);a=Fa(a);b=Fa(b);m>2]=a,I[c+4>>2]=b):(I[c>>2]=b,I[c+4>>2]=a)}function ac(a,b,c){ac.Ha||(ac.Ha=!0,$b(a,b,c))}var bc; +bc=qa?()=>{var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:()=>performance.now();var cc={};function dc(){if(!ec){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:oa||"./this.program"},b;for(b in cc)void 0===cc[b]?delete a[b]:a[b]=cc[b];var c=[];for(b in a)c.push(b+"="+a[b]);ec=c}return ec}var ec; +function wb(a,b,c,d){a||(a=this);this.parent=a;this.ma=a.ma;this.pa=null;this.id=qb++;this.name=b;this.mode=c;this.ea={};this.fa={};this.rdev=d}Object.defineProperties(wb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}});Pb();S=Array(4096);Eb(N,"/");V("/tmp");V("/home");V("/home/web_user"); +(()=>{V("/dev");fb(259,{read:()=>0,write:(b,c,d,e)=>e});Gb("/dev/null",259);eb(1280,hb);eb(1536,ib);Gb("/dev/tty",1280);Gb("/dev/tty1",1536);var a=bb();Wb("random",a);Wb("urandom",a);V("/dev/shm");V("/dev/shm/tmp")})();(()=>{V("/proc");var a=V("/proc/self");V("/proc/self/fd");Eb({ma:()=>{var b=mb(a,"fd",16895,73);b.ea={lookup:(c,d)=>{var e=R[+d];if(!e)throw new M(8);c={parent:null,ma:{Fa:"fake"},ea:{readlink:()=>e.path}};return c.parent=c}};return b}},"/proc/self/fd")})(); +var hc={u:function(a,b){try{a=F(a);if(b&-8)var c=-28;else{var d=T(a,{na:!0}).node;d?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&U(d,a)?-2:0):c=-44}return c}catch(e){if("undefined"===typeof X||!(e instanceof M))throw e;return-e.ha}},K:function(a,b){try{return a=F(a),Jb(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},I:function(a){try{return a=F(a),Kb(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},i:function(a,b){try{var c= +R[a];if(!c)throw new M(8);Jb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},J:function(a){try{var b=R[a];if(!b)throw new M(8);Kb(b.node);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},a:function(a,b,c){Zb=c;try{var d=Z(a);switch(b){case 0:var e=Y();return 0>e?-28:n(d.path,d.flags,0,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=Y(),d.flags|=e,0;case 5:return e=Y(),Ia[e+0>>1]=2,0;case 6:case 7:return 0; +case 16:case 8:return-28;case 9:return I[fc()>>2]=28,-1;default:return-28}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},F:function(a,b){try{var c=Z(a);return Yb(r,c.path,b)}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},B:function(a,b,c,d){try{b=F(b);var e=d&256;d&=4096;var f=b;if("/"===f[0])b=f;else{if(-100===a)var k="/";else{var m=R[a];if(!m)throw new M(8);k=m.path}if(0==f.length){if(!d)throw new M(44);b=k}else b=L(k+"/"+f)}return Yb(e? +Ib:r,b,c)}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},z:function(a,b){try{var c=R[a];if(!c)throw new M(8);if(0===(c.flags&2097155))throw new M(28);Lb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},y:function(a,b){try{if(0===b)return-28;if(b>2]=0;case 21520:return d.tty?-28:-59;case 21531:a=e=Y();if(!d.fa.Ja)throw new M(59);return d.fa.Ja(d,b,a);case 21523:return d.tty?0:-59;case 21524:return d.tty?0:-59;default:B("bad ioctl syscall "+b)}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},C:function(a,b){try{return a=F(a),Yb(Ib,a,b)}catch(c){if("undefined"===typeof X|| +!(c instanceof M))throw c;return-c.ha}},s:function(a,b){try{return a=F(a),a=L(a),"/"===a[a.length-1]&&(a=a.substr(0,a.length-1)),V(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},r:function(a,b,c,d,e,f){try{a:{f<<=12;var k=!1;if(0!==(d&16)&&0!==a%65536)var m=-28;else{if(0!==(d&32)){var p=jb(b);if(!p){m=-48;break a}k=!0}else{var q=R[e];if(!q){m=-8;break a}var t=f;if(0!==(c&2)&&0===(d&2)&&2!==(q.flags&2097155))throw new M(2);if(1===(q.flags&2097155))throw new M(2); +if(!q.fa.ta)throw new M(43);var v=q.fa.ta(q,a,b,t,c,d);p=v.Oa;k=v.va}Xb[p]={La:p,Ka:b,va:k,fd:e,Na:c,flags:d,offset:f};m=p}}return m}catch(H){if("undefined"===typeof X||!(H instanceof M))throw H;return-H.ha}},q:function(a,b){try{var c=Xb[a];if(0!==b&&c){if(b===c.Ka){var d=R[c.fd];if(d&&c.Na&2){var e=c.flags,f=c.offset,k=l.slice(a,a+b);d&&d.fa.ua&&d.fa.ua(d,k,f,b,e)}Xb[a]=null;c.va&&gc(c.La)}var m=0}else m=-28;return m}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},h:function(a, +b,c){Zb=c;try{var d=F(a),e=c?Y():0;return n(d,b,e).fd}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},p:function(a,b,c){try{a=F(a);if(0>=c)var d=-28;else{var e=sb(a),f=Math.min(c,Ea(e)),k=E[b+f];D(e,l,b,c+1);E[b+f]=k;d=f}return d}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return-m.ha}},o:function(a){try{a=F(a);var b=T(a,{parent:!0}).node,c=ab(a),d=O(b,c),e=Ab(b,c,!0);if(e)throw new M(e);if(!b.ea.rmdir)throw new M(63);if(d.pa)throw new M(10);b.ea.rmdir(b, +c);vb(d);return 0}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},D:function(a,b){try{return a=F(a),Yb(r,a,b)}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},n:function(a){try{return a=F(a),ka(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},d:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")},l:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}, +j:function(a,b){a=new Date(1E3*I[a>>2]);I[b>>2]=a.getSeconds();I[b+4>>2]=a.getMinutes();I[b+8>>2]=a.getHours();I[b+12>>2]=a.getDate();I[b+16>>2]=a.getMonth();I[b+20>>2]=a.getFullYear()-1900;I[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);I[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;I[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();c=c.getTimezoneOffset();I[b+32>>2]=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0},k:ac,e:bc,G:function(a,b,c){l.copyWithin(a, +b,b+c)},c:function(a){var b=l.length;a>>>=0;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a,d);0>>16);Ka();var e=1;break a}catch(f){}e=void 0}if(e)return!0}return!1},w:function(a,b){var c=0;dc().forEach(function(d,e){var f=b+c;e=I[a+4*e>>2]=f;for(f=0;f>0]=d.charCodeAt(f);E[e>>0]=0;c+=d.length+1});return 0},x:function(a,b){var c=dc();I[a>> +2]=c.length;var d=0;c.forEach(function(e){d+=e.length+1});I[b>>2]=d;return 0},b:function(a){try{var b=Z(a);ja(b);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},m:function(a,b){try{var c=Z(a);E[b>>0]=c.tty?2:16384===(c.mode&61440)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return d.ha}},g:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],m=ia(e,E,I[b+8*f>>2],k,void 0);if(0>m){var p= +-1;break a}a+=m;if(m>2]=p;return 0}catch(q){if("undefined"===typeof X||!(q instanceof M))throw q;return q.ha}},t:function(a,b,c,d){try{var e=Number(b&BigInt(4294967295))|0,f=Number(b>>BigInt(32))|0,k=Z(a);a=4294967296*f+(e>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Nb(k,a,c);Ja[d>>3]=BigInt(k.position);k.xa&&0===a&&0===c&&(k.xa=null);return 0}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return m.ha}},A:function(a){try{var b=Z(a);return b.fa&&b.fa.fsync? +-b.fa.fsync(b):0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},f:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],I[b+(8*f+4)>>2]);if(0>k){var m=-1;break a}a+=k}m=a}I[d>>2]=m;return 0}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return p.ha}},E:function(a){var b=Date.now();I[a>>2]=b/1E3|0;I[a+4>>2]=b%1E3*1E3|0;return 0},L:function(a){var b=Date.now()/1E3|0;a&&(I[a>>2]=b);return b},M:function(a,b){if(b){var c=b+8;b=1E3*I[c>> +2];b+=I[c+4>>2]/1E3}else b=Date.now();a=F(a);try{var d=T(a,{na:!0}).node;d.ea.ja(d,{timestamp:Math.max(b,b)});var e=0}catch(f){if(!(f instanceof M)){b:{e=Error();if(!e.stack){try{throw Error();}catch(k){e=k}if(!e.stack){e="(no stack trace available)";break b}}e=e.stack.toString()}g.extraStackTrace&&(e+="\n"+g.extraStackTrace());e=Ya(e);throw f+" : "+e;}e=f.ha;I[fc()>>2]=e;e=-1}return e}}; +(function(){function a(e){g.asm=e.exports;xa=g.asm.N;Ka();La=g.asm.$;Na.unshift(g.asm.O);J--;g.monitorRunDependencies&&g.monitorRunDependencies(J);0==J&&(null!==Qa&&(clearInterval(Qa),Qa=null),Ra&&(e=Ra,Ra=null,e()))}function b(e){a(e.instance)}function c(e){return Wa().then(function(f){return WebAssembly.instantiate(f,d)}).then(function(f){return f}).then(e,function(f){y("failed to asynchronously prepare wasm: "+f);B(f)})}var d={a:hc};J++;g.monitorRunDependencies&&g.monitorRunDependencies(J);if(g.instantiateWasm)try{return g.instantiateWasm(d, +a)}catch(e){return y("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||Sa()||K.startsWith("file://")||"function"!==typeof fetch?c(b):fetch(K,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(f){y("wasm streaming compile failed: "+f);y("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +g.___wasm_call_ctors=function(){return(g.___wasm_call_ctors=g.asm.O).apply(null,arguments)};var fa=g._dbCall=function(){return(fa=g._dbCall=g.asm.P).apply(null,arguments)};g._jsbatonGetString=function(){return(g._jsbatonGetString=g.asm.Q).apply(null,arguments)};var ca=g._sqlite3_malloc=function(){return(ca=g._sqlite3_malloc=g.asm.R).apply(null,arguments)},ha=g._sqlite3_free=function(){return(ha=g._sqlite3_free=g.asm.S).apply(null,arguments)}; +g._dbFileLoadOrSave=function(){return(g._dbFileLoadOrSave=g.asm.T).apply(null,arguments)};var la=g._jsbatonGetInt64=function(){return(la=g._jsbatonGetInt64=g.asm.U).apply(null,arguments)};g._sqlite3_errmsg=function(){return(g._sqlite3_errmsg=g.asm.V).apply(null,arguments)};g._jsbatonGetErrmsg=function(){return(g._jsbatonGetErrmsg=g.asm.W).apply(null,arguments)}; +var ma=g._sqlite3_initialize=function(){return(ma=g._sqlite3_initialize=g.asm.X).apply(null,arguments)},fc=g.___errno_location=function(){return(fc=g.___errno_location=g.asm.Y).apply(null,arguments)},Ga=g._malloc=function(){return(Ga=g._malloc=g.asm.Z).apply(null,arguments)},gc=g._free=function(){return(gc=g._free=g.asm._).apply(null,arguments)},kb=g._memalign=function(){return(kb=g._memalign=g.asm.aa).apply(null,arguments)},Ba=g.stackSave=function(){return(Ba=g.stackSave=g.asm.ba).apply(null,arguments)}, +Ca=g.stackRestore=function(){return(Ca=g.stackRestore=g.asm.ca).apply(null,arguments)},Aa=g.stackAlloc=function(){return(Aa=g.stackAlloc=g.asm.da).apply(null,arguments)};g.cwrap=h;var ic;Ra=function jc(){ic||kc();ic||(Ra=jc)}; +function kc(){function a(){if(!ic&&(ic=!0,g.calledRun=!0,!ya)){g.noFSInit||Qb||(Qb=!0,Pb(),g.stdin=g.stdin,g.stdout=g.stdout,g.stderr=g.stderr,g.stdin?Wb("stdin",g.stdin):Hb("/dev/tty","/dev/stdin"),g.stdout?Wb("stdout",null,g.stdout):Hb("/dev/tty","/dev/stdout"),g.stderr?Wb("stderr",null,g.stderr):Hb("/dev/tty1","/dev/stderr"),n("/dev/stdin",0),n("/dev/stdout",1),n("/dev/stderr",1));rb=!1;Xa(Na);if(g.onRuntimeInitialized)g.onRuntimeInitialized();if(g.postRun)for("function"==typeof g.postRun&&(g.postRun= +[g.postRun]);g.postRun.length;){var b=g.postRun.shift();Oa.unshift(b)}Xa(Oa)}}if(!(0{ua||(fs=require("fs"),ua=require("path"))},ra=function(a,b){va();a=ua.normalize(a);return fs.readFileSync(a,b?null:"utf8")},ta=a=>{a=ra(a,!0);a.buffer||(a=new Uint8Array(a));return a},sa=(a,b,c)=>{va();a=ua.normalize(a);fs.readFile(a,function(d,e){d?c(d):b(e.buffer)})},1{var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},w&&(ta=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),sa=(a,b,c)=>{var d=new XMLHttpRequest;d.open("GET",a,!0);d.responseType="arraybuffer"; +d.onload=()=>{200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var wa=g.print||console.log.bind(console),y=g.printErr||console.warn.bind(console);aa(g,na);na=null;g.thisProgram&&(oa=g.thisProgram);var z;g.wasmBinary&&(z=g.wasmBinary);var noExitRuntime=g.noExitRuntime||!0;"object"!==typeof WebAssembly&&B("no native wasm support detected");var xa,ya=!1; +function za(a,b,c,d){var e={string:function(q){var t=0;if(null!==q&&void 0!==q&&0!==q){var v=(q.length<<2)+1;t=Aa(v);D(q,l,t,v)}return t},array:function(q){var t=Aa(q.length);E.set(q,t);return t}};a=g["_"+a];var f=[],k=0;if(d)for(var m=0;m=d);)++c;if(16e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}function F(a){return a?u(l,a,void 0):""} +function D(a,b,c,d){if(!(0=k){var m=a.charCodeAt(++f);k=65536+((k&1023)<<10)|m&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-e} +function Ea(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Fa(a){var b=Ea(a)+1,c=Ga(b);c&&D(a,E,c,b);return c}var Ha,E,l,Ia,I,Ja; +function Ka(){var a=xa.buffer;Ha=a;g.HEAP8=E=new Int8Array(a);g.HEAP16=Ia=new Int16Array(a);g.HEAP32=I=new Int32Array(a);g.HEAPU8=l=new Uint8Array(a);g.HEAPU16=new Uint16Array(a);g.HEAPU32=new Uint32Array(a);g.HEAPF32=new Float32Array(a);g.HEAPF64=new Float64Array(a);g.HEAP64=Ja=new BigInt64Array(a);g.HEAPU64=new BigUint64Array(a)}var La,Ma=[],Na=[],Oa=[];function Pa(){var a=g.preRun.shift();Ma.unshift(a)}var J=0,Qa=null,Ra=null;g.preloadedImages={};g.preloadedAudios={}; +function B(a){if(g.onAbort)g.onAbort(a);a="Aborted("+a+")";y(a);ya=!0;throw new WebAssembly.RuntimeError(a+". Build with -s ASSERTIONS=1 for more info.");}function Sa(){return K.startsWith("data:application/octet-stream;base64,")}var K;K="sqlmath_wasm.wasm";if(!Sa()){var Ta=K;K=g.locateFile?g.locateFile(Ta,x):x+Ta}function Ua(){var a=K;try{if(a==K&&z)return new Uint8Array(z);if(ta)return ta(a);throw"both async and sync fetching of the wasm failed";}catch(b){B(b)}} +function Wa(){if(!z&&(pa||w)){if("function"===typeof fetch&&!K.startsWith("file://"))return fetch(K,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+K+"'";return a.arrayBuffer()}).catch(function(){return Ua()});if(sa)return new Promise(function(a,b){sa(K,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return Ua()})} +function Xa(a){for(;0=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.ga,a.ga=new Uint8Array(b),0=a.node.ia)return 0;a=Math.min(a.node.ia-e,d);if(8b)throw new M(28);return b},Aa:function(a,b,c){N.Ca(a.node,b+c);a.node.ia=Math.max(a.node.ia,b+c)},ta:function(a,b,c,d,e,f){if(0!==b)throw new M(28);if(32768!==(a.node.mode&61440))throw new M(43); +a=a.node.ga;if(f&2||a.buffer!==Ha){if(0{a=cb("/",a);if(!a)return{path:"",node:null};var c={Da:!0,za:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]); +if(8!!k),!1);var e=ob;c="/";for(d=0;d{for(var b;;){if(a===a.parent)return a=a.ma.Fa,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}},ub=(a,b)=>{for(var c=0,d=0;d>>0)%S.length},vb=a=>{var b=ub(a.parent.id,a.name);if(S[b]===a)S[b]=a.qa;else for(b=S[b];b;){if(b.qa===a){b.qa=a.qa;break}b=b.qa}},O=(a,b)=>{var c;if(c=(c=U(a,"x"))?c:a.ea.lookup?0:2)throw new M(c,a);for(c=S[ub(a.id,b)];c;c=c.qa){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.ea.lookup(a,b)},mb=(a,b,c,d)=>{a=new wb(a,b,c,d);b=ub(a.parent.id,a.name);a.qa=S[b];return S[b]=a},xb={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},yb=a=>{var b=["r","w","rw"][a& +3];a&512&&(b+="w");return b},U=(a,b)=>{if(rb)return 0;if(!b.includes("r")||a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0},zb=(a,b)=>{try{return O(a,b),20}catch(c){}return U(a,"wx")},Ab=(a,b,c)=>{try{var d=O(a,b)}catch(e){return e.ha}if(a=U(a,"wx"))return a;if(c){if(16384!==(d.mode&61440))return 54;if(d===d.parent||"/"===tb(d))return 10}else if(16384===(d.mode&61440))return 31;return 0},Bb=(a=0,b=4096)=>{for(;a<=b;a++)if(!R[a])return a; +throw new M(33);},Db=(a,b)=>{Cb||(Cb=function(){},Cb.prototype={});var c=new Cb,d;for(d in a)c[d]=a[d];a=c;b=Bb(b,void 0);a.fd=b;return R[b]=a},lb={open:a=>{a.fa=pb[a.node.rdev].fa;a.fa.open&&a.fa.open(a)},oa:()=>{throw new M(70);}},fb=(a,b)=>{pb[a]={fa:b}},Eb=(a,b)=>{var c="/"===b,d=!b;if(c&&ob)throw new M(10);if(!c&&!d){var e=T(b,{Da:!1});b=e.path;e=e.node;if(e.pa)throw new M(10);if(16384!==(e.mode&61440))throw new M(54);}b={type:a,Ua:{},Fa:b,Ma:[]};a=a.ma(b);a.ma=b;b.root=a;c?ob=a:e&&(e.pa=b,e.ma&& +e.ma.Ma.push(b))},Fb=(a,b,c)=>{var d=T(a,{parent:!0}).node;a=ab(a);if(!a||"."===a||".."===a)throw new M(28);var e=zb(d,a);if(e)throw new M(e);if(!d.ea.sa)throw new M(63);return d.ea.sa(d,a,b,c)},V=(a,b)=>Fb(a,(void 0!==b?b:511)&1023|16384,0),Gb=(a,b,c)=>{"undefined"===typeof c&&(c=b,b=438);Fb(a,b|8192,c)},Hb=(a,b)=>{if(!cb(a))throw new M(44);var c=T(b,{parent:!0}).node;if(!c)throw new M(44);b=ab(b);var d=zb(c,b);if(d)throw new M(d);if(!c.ea.symlink)throw new M(63);c.ea.symlink(c,b,a)},ka=a=>{var b= +T(a,{parent:!0}).node;if(!b)throw new M(44);a=ab(a);var c=O(b,a),d=Ab(b,a,!1);if(d)throw new M(d);if(!b.ea.unlink)throw new M(63);if(c.pa)throw new M(10);b.ea.unlink(b,a);vb(c)},sb=a=>{a=T(a).node;if(!a)throw new M(44);if(!a.ea.readlink)throw new M(28);return cb(tb(a.parent),a.ea.readlink(a))},r=(a,b)=>{a=T(a,{na:!b}).node;if(!a)throw new M(44);if(!a.ea.ka)throw new M(63);return a.ea.ka(a)},Ib=a=>r(a,!0),Jb=(a,b)=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{mode:b& +4095|a.mode&-4096,timestamp:Date.now()})},Kb=a=>{a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);a.ea.ja(a,{timestamp:Date.now()})},Lb=(a,b)=>{if(0>b)throw new M(28);a="string"===typeof a?T(a,{na:!0}).node:a;if(!a.ea.ja)throw new M(63);if(16384===(a.mode&61440))throw new M(31);if(32768!==(a.mode&61440))throw new M(28);var c=U(a,"w");if(c)throw new M(c);a.ea.ja(a,{size:b,timestamp:Date.now()})},n=(a,b,c,d)=>{if(""===a)throw new M(44);if("string"===typeof b){var e=xb[b];if("undefined"=== +typeof e)throw Error("Unknown file open mode: "+b);b=e}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var f=a;else{a=L(a);try{f=T(a,{na:!(b&131072)}).node}catch(k){}}e=!1;if(b&64)if(f){if(b&128)throw new M(20);}else f=Fb(a,c,0),e=!0;if(!f)throw new M(44);8192===(f.mode&61440)&&(b&=-513);if(b&65536&&16384!==(f.mode&61440))throw new M(54);if(!e&&(c=f?40960===(f.mode&61440)?32:16384===(f.mode&61440)&&("r"!==yb(b)||b&512)?31:U(f,yb(b)):44))throw new M(c);b&512&&Lb(f,0);b&=-131713; +d=Db({node:f,path:tb(f),flags:b,seekable:!0,position:0,fa:f.fa,Ra:[],error:!1},d);d.fa.open&&d.fa.open(d);!g.logReadFiles||b&1||(Mb||(Mb={}),a in Mb||(Mb[a]=1));return d},ja=a=>{if(null===a.fd)throw new M(8);a.xa&&(a.xa=null);try{a.fa.close&&a.fa.close(a)}catch(b){throw b;}finally{R[a.fd]=null}a.fd=null},Nb=(a,b,c)=>{if(null===a.fd)throw new M(8);if(!a.seekable||!a.fa.oa)throw new M(70);if(0!=c&&1!=c&&2!=c)throw new M(28);a.position=a.fa.oa(a,b,c);a.Ra=[]},ia=(a,b,c,d,e)=>{if(0>d||0>e)throw new M(28); +if(null===a.fd)throw new M(8);if(1===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.read)throw new M(28);var f="undefined"!==typeof e;if(!f)e=a.position;else if(!a.seekable)throw new M(70);b=a.fa.read(a,b,c,d,e);f||(a.position+=b);return b},Ob=(a,b,c,d,e)=>{var f=void 0;if(0>d||0>f)throw new M(28);if(null===a.fd)throw new M(8);if(0===(a.flags&2097155))throw new M(8);if(16384===(a.node.mode&61440))throw new M(31);if(!a.fa.write)throw new M(28);a.seekable&& +a.flags&1024&&Nb(a,0,2);var k="undefined"!==typeof f;if(!k)f=a.position;else if(!a.seekable)throw new M(70);b=a.fa.write(a,b,c,d,f,e);k||(a.position+=b);return b},da=(a,b)=>{var c={};c.flags=c.flags||577;a=n(a,c.flags,c.mode);if("string"===typeof b){var d=new Uint8Array(Ea(b)+1);b=D(b,d,0,d.length);Ob(a,d,0,b,c.Ia)}else if(ArrayBuffer.isView(b))Ob(a,b,0,b.byteLength,c.Ia);else throw Error("Unsupported data type");ja(a)},Pb=()=>{M||(M=function(a,b){this.node=b;this.Qa=function(c){this.ha=c};this.Qa(a); +this.message="FS error"},M.prototype=Error(),M.prototype.constructor=M,[44].forEach(a=>{nb[a]=new M(a);nb[a].stack=""}))},Qb,Ub=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},Wb=(a,b,c)=>{a=L("/dev/"+a);var d=Ub(!!b,!!c);Vb||(Vb=64);var e=Vb++<<8|0;fb(e,{open:f=>{f.seekable=!1},close:()=>{c&&c.buffer&&c.buffer.length&&c(10)},read:(f,k,m,p)=>{for(var q=0,t=0;t{for(var q=0;q>2]=d.dev;I[c+4>>2]=0;I[c+8>>2]=d.ino;I[c+12>>2]=d.mode;I[c+16>>2]=d.nlink;I[c+20>>2]=d.uid;I[c+24>>2]=d.gid;I[c+28>>2]=d.rdev;I[c+32>>2]=0;Ja[c+40>>3]=BigInt(d.size);I[c+48>>2]=4096;I[c+52>>2]=d.blocks;I[c+56>>2]=d.atime.getTime()/1E3|0;I[c+60>>2]=0;I[c+64>>2]=d.mtime.getTime()/1E3|0;I[c+68>>2]=0;I[c+72>>2]=d.ctime.getTime()/1E3|0;I[c+76>>2]=0;Ja[c+80>>3]=BigInt(d.ino);return 0}var Zb=void 0; +function Y(){Zb+=4;return I[Zb-4>>2]}function Z(a){a=R[a];if(!a)throw new M(8);return a}function $b(a,b,c){function d(p){return(p=p.toTimeString().match(/\(([A-Za-z ]+)\)$/))?p[1]:"GMT"}var e=(new Date).getFullYear(),f=new Date(e,0,1),k=new Date(e,6,1);e=f.getTimezoneOffset();var m=k.getTimezoneOffset();I[a>>2]=60*Math.max(e,m);I[b>>2]=Number(e!=m);a=d(f);b=d(k);a=Fa(a);b=Fa(b);m>2]=a,I[c+4>>2]=b):(I[c>>2]=b,I[c+4>>2]=a)}function ac(a,b,c){ac.Ha||(ac.Ha=!0,$b(a,b,c))}var bc; +bc=qa?()=>{var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:()=>performance.now();var cc={};function dc(){if(!ec){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:oa||"./this.program"},b;for(b in cc)void 0===cc[b]?delete a[b]:a[b]=cc[b];var c=[];for(b in a)c.push(b+"="+a[b]);ec=c}return ec}var ec; +function wb(a,b,c,d){a||(a=this);this.parent=a;this.ma=a.ma;this.pa=null;this.id=qb++;this.name=b;this.mode=c;this.ea={};this.fa={};this.rdev=d}Object.defineProperties(wb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}});Pb();S=Array(4096);Eb(N,"/");V("/tmp");V("/home");V("/home/web_user"); +(()=>{V("/dev");fb(259,{read:()=>0,write:(b,c,d,e)=>e});Gb("/dev/null",259);eb(1280,hb);eb(1536,ib);Gb("/dev/tty",1280);Gb("/dev/tty1",1536);var a=bb();Wb("random",a);Wb("urandom",a);V("/dev/shm");V("/dev/shm/tmp")})();(()=>{V("/proc");var a=V("/proc/self");V("/proc/self/fd");Eb({ma:()=>{var b=mb(a,"fd",16895,73);b.ea={lookup:(c,d)=>{var e=R[+d];if(!e)throw new M(8);c={parent:null,ma:{Fa:"fake"},ea:{readlink:()=>e.path}};return c.parent=c}};return b}},"/proc/self/fd")})(); +var hc={u:function(a,b){try{a=F(a);if(b&-8)var c=-28;else{var d=T(a,{na:!0}).node;d?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&U(d,a)?-2:0):c=-44}return c}catch(e){if("undefined"===typeof X||!(e instanceof M))throw e;return-e.ha}},K:function(a,b){try{return a=F(a),Jb(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},I:function(a){try{return a=F(a),Kb(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},i:function(a,b){try{var c= +R[a];if(!c)throw new M(8);Jb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},J:function(a){try{var b=R[a];if(!b)throw new M(8);Kb(b.node);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},a:function(a,b,c){Zb=c;try{var d=Z(a);switch(b){case 0:var e=Y();return 0>e?-28:n(d.path,d.flags,0,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=Y(),d.flags|=e,0;case 5:return e=Y(),Ia[e+0>>1]=2,0;case 6:case 7:return 0; +case 16:case 8:return-28;case 9:return I[fc()>>2]=28,-1;default:return-28}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},F:function(a,b){try{var c=Z(a);return Yb(r,c.path,b)}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},B:function(a,b,c,d){try{b=F(b);var e=d&256;d&=4096;var f=b;if("/"===f[0])b=f;else{if(-100===a)var k="/";else{var m=R[a];if(!m)throw new M(8);k=m.path}if(0==f.length){if(!d)throw new M(44);b=k}else b=L(k+"/"+f)}return Yb(e? +Ib:r,b,c)}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},z:function(a,b){try{var c=R[a];if(!c)throw new M(8);if(0===(c.flags&2097155))throw new M(28);Lb(c.node,b);return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return-d.ha}},y:function(a,b){try{if(0===b)return-28;if(b>2]=0;case 21520:return d.tty?-28:-59;case 21531:a=e=Y();if(!d.fa.Ja)throw new M(59);return d.fa.Ja(d,b,a);case 21523:return d.tty?0:-59;case 21524:return d.tty?0:-59;default:B("bad ioctl syscall "+b)}}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},C:function(a,b){try{return a=F(a),Yb(Ib,a,b)}catch(c){if("undefined"===typeof X|| +!(c instanceof M))throw c;return-c.ha}},s:function(a,b){try{return a=F(a),a=L(a),"/"===a[a.length-1]&&(a=a.substr(0,a.length-1)),V(a,b),0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},r:function(a,b,c,d,e,f){try{a:{f<<=12;var k=!1;if(0!==(d&16)&&0!==a%65536)var m=-28;else{if(0!==(d&32)){var p=jb(b);if(!p){m=-48;break a}k=!0}else{var q=R[e];if(!q){m=-8;break a}var t=f;if(0!==(c&2)&&0===(d&2)&&2!==(q.flags&2097155))throw new M(2);if(1===(q.flags&2097155))throw new M(2); +if(!q.fa.ta)throw new M(43);var v=q.fa.ta(q,a,b,t,c,d);p=v.Oa;k=v.va}Xb[p]={La:p,Ka:b,va:k,fd:e,Na:c,flags:d,offset:f};m=p}}return m}catch(H){if("undefined"===typeof X||!(H instanceof M))throw H;return-H.ha}},q:function(a,b){try{var c=Xb[a];if(0!==b&&c){if(b===c.Ka){var d=R[c.fd];if(d&&c.Na&2){var e=c.flags,f=c.offset,k=l.slice(a,a+b);d&&d.fa.ua&&d.fa.ua(d,k,f,b,e)}Xb[a]=null;c.va&&gc(c.La)}var m=0}else m=-28;return m}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return-p.ha}},h:function(a, +b,c){Zb=c;try{var d=F(a),e=c?Y():0;return n(d,b,e).fd}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},p:function(a,b,c){try{a=F(a);if(0>=c)var d=-28;else{var e=sb(a),f=Math.min(c,Ea(e)),k=E[b+f];D(e,l,b,c+1);E[b+f]=k;d=f}return d}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return-m.ha}},o:function(a){try{a=F(a);var b=T(a,{parent:!0}).node,c=ab(a),d=O(b,c),e=Ab(b,c,!0);if(e)throw new M(e);if(!b.ea.rmdir)throw new M(63);if(d.pa)throw new M(10);b.ea.rmdir(b, +c);vb(d);return 0}catch(f){if("undefined"===typeof X||!(f instanceof M))throw f;return-f.ha}},D:function(a,b){try{return a=F(a),Yb(r,a,b)}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return-c.ha}},n:function(a){try{return a=F(a),ka(a),0}catch(b){if("undefined"===typeof X||!(b instanceof M))throw b;return-b.ha}},d:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")},l:function(){B("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}, +j:function(a,b){a=new Date(1E3*I[a>>2]);I[b>>2]=a.getSeconds();I[b+4>>2]=a.getMinutes();I[b+8>>2]=a.getHours();I[b+12>>2]=a.getDate();I[b+16>>2]=a.getMonth();I[b+20>>2]=a.getFullYear()-1900;I[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);I[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;I[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();c=c.getTimezoneOffset();I[b+32>>2]=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0},k:ac,e:bc,G:function(a,b,c){l.copyWithin(a, +b,b+c)},c:function(a){var b=l.length;a>>>=0;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a,d);0>>16);Ka();var e=1;break a}catch(f){}e=void 0}if(e)return!0}return!1},w:function(a,b){var c=0;dc().forEach(function(d,e){var f=b+c;e=I[a+4*e>>2]=f;for(f=0;f>0]=d.charCodeAt(f);E[e>>0]=0;c+=d.length+1});return 0},x:function(a,b){var c=dc();I[a>> +2]=c.length;var d=0;c.forEach(function(e){d+=e.length+1});I[b>>2]=d;return 0},b:function(a){try{var b=Z(a);ja(b);return 0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},m:function(a,b){try{var c=Z(a);E[b>>0]=c.tty?2:16384===(c.mode&61440)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){if("undefined"===typeof X||!(d instanceof M))throw d;return d.ha}},g:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],m=ia(e,E,I[b+8*f>>2],k,void 0);if(0>m){var p= +-1;break a}a+=m;if(m>2]=p;return 0}catch(q){if("undefined"===typeof X||!(q instanceof M))throw q;return q.ha}},t:function(a,b,c,d){try{var e=Number(b&BigInt(4294967295))|0,f=Number(b>>BigInt(32))|0,k=Z(a);a=4294967296*f+(e>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Nb(k,a,c);Ja[d>>3]=BigInt(k.position);k.xa&&0===a&&0===c&&(k.xa=null);return 0}catch(m){if("undefined"===typeof X||!(m instanceof M))throw m;return m.ha}},A:function(a){try{var b=Z(a);return b.fa&&b.fa.fsync? +-b.fa.fsync(b):0}catch(c){if("undefined"===typeof X||!(c instanceof M))throw c;return c.ha}},f:function(a,b,c,d){try{a:{for(var e=Z(a),f=a=0;f>2],I[b+(8*f+4)>>2]);if(0>k){var m=-1;break a}a+=k}m=a}I[d>>2]=m;return 0}catch(p){if("undefined"===typeof X||!(p instanceof M))throw p;return p.ha}},E:function(a){var b=Date.now();I[a>>2]=b/1E3|0;I[a+4>>2]=b%1E3*1E3|0;return 0},L:function(a){var b=Date.now()/1E3|0;a&&(I[a>>2]=b);return b},M:function(a,b){if(b){var c=b+8;b=1E3*I[c>> +2];b+=I[c+4>>2]/1E3}else b=Date.now();a=F(a);try{var d=T(a,{na:!0}).node;d.ea.ja(d,{timestamp:Math.max(b,b)});var e=0}catch(f){if(!(f instanceof M)){b:{e=Error();if(!e.stack){try{throw Error();}catch(k){e=k}if(!e.stack){e="(no stack trace available)";break b}}e=e.stack.toString()}g.extraStackTrace&&(e+="\n"+g.extraStackTrace());e=Ya(e);throw f+" : "+e;}e=f.ha;I[fc()>>2]=e;e=-1}return e}}; +(function(){function a(e){g.asm=e.exports;xa=g.asm.N;Ka();La=g.asm.$;Na.unshift(g.asm.O);J--;g.monitorRunDependencies&&g.monitorRunDependencies(J);0==J&&(null!==Qa&&(clearInterval(Qa),Qa=null),Ra&&(e=Ra,Ra=null,e()))}function b(e){a(e.instance)}function c(e){return Wa().then(function(f){return WebAssembly.instantiate(f,d)}).then(function(f){return f}).then(e,function(f){y("failed to asynchronously prepare wasm: "+f);B(f)})}var d={a:hc};J++;g.monitorRunDependencies&&g.monitorRunDependencies(J);if(g.instantiateWasm)try{return g.instantiateWasm(d, +a)}catch(e){return y("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||Sa()||K.startsWith("file://")||"function"!==typeof fetch?c(b):fetch(K,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(f){y("wasm streaming compile failed: "+f);y("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +g.___wasm_call_ctors=function(){return(g.___wasm_call_ctors=g.asm.O).apply(null,arguments)};var fa=g._dbCall=function(){return(fa=g._dbCall=g.asm.P).apply(null,arguments)};g._jsbatonGetString=function(){return(g._jsbatonGetString=g.asm.Q).apply(null,arguments)};var ca=g._sqlite3_malloc=function(){return(ca=g._sqlite3_malloc=g.asm.R).apply(null,arguments)},ha=g._sqlite3_free=function(){return(ha=g._sqlite3_free=g.asm.S).apply(null,arguments)}; +g._dbFileLoadOrSave=function(){return(g._dbFileLoadOrSave=g.asm.T).apply(null,arguments)};var la=g._jsbatonGetInt64=function(){return(la=g._jsbatonGetInt64=g.asm.U).apply(null,arguments)};g._sqlite3_errmsg=function(){return(g._sqlite3_errmsg=g.asm.V).apply(null,arguments)};g._jsbatonGetErrmsg=function(){return(g._jsbatonGetErrmsg=g.asm.W).apply(null,arguments)}; +var ma=g._sqlite3_initialize=function(){return(ma=g._sqlite3_initialize=g.asm.X).apply(null,arguments)},fc=g.___errno_location=function(){return(fc=g.___errno_location=g.asm.Y).apply(null,arguments)},Ga=g._malloc=function(){return(Ga=g._malloc=g.asm.Z).apply(null,arguments)},gc=g._free=function(){return(gc=g._free=g.asm._).apply(null,arguments)},kb=g._memalign=function(){return(kb=g._memalign=g.asm.aa).apply(null,arguments)},Ba=g.stackSave=function(){return(Ba=g.stackSave=g.asm.ba).apply(null,arguments)}, +Ca=g.stackRestore=function(){return(Ca=g.stackRestore=g.asm.ca).apply(null,arguments)},Aa=g.stackAlloc=function(){return(Aa=g.stackAlloc=g.asm.da).apply(null,arguments)};g.cwrap=h;var ic;Ra=function jc(){ic||kc();ic||(Ra=jc)}; +function kc(){function a(){if(!ic&&(ic=!0,g.calledRun=!0,!ya)){g.noFSInit||Qb||(Qb=!0,Pb(),g.stdin=g.stdin,g.stdout=g.stdout,g.stderr=g.stderr,g.stdin?Wb("stdin",g.stdin):Hb("/dev/tty","/dev/stdin"),g.stdout?Wb("stdout",null,g.stdout):Hb("/dev/tty","/dev/stdout"),g.stderr?Wb("stderr",null,g.stderr):Hb("/dev/tty1","/dev/stderr"),n("/dev/stdin",0),n("/dev/stdout",1),n("/dev/stderr",1));rb=!1;Xa(Na);if(g.onRuntimeInitialized)g.onRuntimeInitialized();if(g.postRun)for("function"==typeof g.postRun&&(g.postRun= +[g.postRun]);g.postRun.length;){var b=g.postRun.shift();Oa.unshift(b)}Xa(Oa)}}if(!(0 cpplint.py -# -# Copyright (c) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Does google-lint on c++ files. - -The goal of this script is to identify places in the code that *may* -be in non-compliance with google style. It does not attempt to fix -up these problems -- the point is to educate. It does also not -attempt to find all problems, or to ensure that everything it does -find is legitimately a problem. - -In particular, we can get very confused by /* and // inside strings! -We do a small hack, which is to ignore //'s with "'s after them on the -same line, but it is far from perfect (in either direction). -""" - -from __future__ import annotations # PEP 604 not in 3.9 - -import codecs -import collections -import copy -import getopt -import glob -import itertools -import math # for log -import os -import re -import string -import sys -import sysconfig -import unicodedata -import xml.etree.ElementTree - -# if empty, use defaults -_valid_extensions: set[str] = set() - -__VERSION__ = "2.0.2" - -_USAGE = """ -Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed] - [--filter=-x,+y,...] - [--counting=total|toplevel|detailed] [--root=subdir] - [--repository=path] - [--linelength=digits] [--headers=x,y,...] - [--recursive] - [--exclude=path] - [--extensions=hpp,cpp,...] - [--includeorder=default|standardcfirst] - [--config=filename] - [--quiet] - [--version] - [file] ... - - Style checker for C/C++ source files. - This is a fork of the Google style checker with minor extensions. - - The style guidelines this tries to follow are those in - https://google.github.io/styleguide/cppguide.html - - Every problem is given a confidence score from 1-5, with 5 meaning we are - certain of the problem, and 1 meaning it could be a legitimate construct. - This will miss some errors, and is not a substitute for a code review. - - To suppress false-positive errors of certain categories, add a - 'NOLINT(category[, category...])' comment to the line. NOLINT or NOLINT(*) - suppresses errors of all categories on that line. To suppress categories - on the next line use NOLINTNEXTLINE instead of NOLINT. To suppress errors in - a block of code 'NOLINTBEGIN(category[, category...])' comment to a line at - the start of the block and to end the block add a comment with 'NOLINTEND'. - NOLINT blocks are inclusive so any statements on the same line as a BEGIN - or END will have the error suppression applied. - - The files passed in will be linted; at least one file must be provided. - Default linted extensions are %s. - Other file types will be ignored. - Change the extensions with the --extensions flag. - - Flags: - - output=emacs|eclipse|vs7|junit|sed|gsed - By default, the output is formatted to ease emacs parsing. Visual Studio - compatible output (vs7) may also be used. Further support exists for - eclipse (eclipse), and JUnit (junit). XML parsers such as those used - in Jenkins and Bamboo may also be used. - The sed format outputs sed commands that should fix some of the errors. - Note that this requires gnu sed. If that is installed as gsed on your - system (common e.g. on macOS with homebrew) you can use the gsed output - format. Sed commands are written to stdout, not stderr, so you should be - able to pipe output straight to a shell to run the fixes. - - verbose=# - Specify a number 0-5 to restrict errors to certain verbosity levels. - Errors with lower verbosity levels have lower confidence and are more - likely to be false positives. - - quiet - Don't print anything if no errors are found. - - filter=-x,+y,... - Specify a comma-separated list of category-filters to apply: only - error messages whose category names pass the filters will be printed. - (Category names are printed with the message and look like - "[whitespace/indent]".) Filters are evaluated left to right. - "-FOO" means "do not print categories that start with FOO". - "+FOO" means "do print categories that start with FOO". - - Examples: --filter=-whitespace,+whitespace/braces - --filter=-whitespace,-runtime/printf,+runtime/printf_format - --filter=-,+build/include_what_you_use - - To see a list of all the categories used in cpplint, pass no arg: - --filter= - - Filters can directly be limited to files and also line numbers. The - syntax is category:file:line , where line is optional. The filter limitation - works for both + and - and can be combined with ordinary filters: - - Examples: --filter=-whitespace:foo.h,+whitespace/braces:foo.h - --filter=-whitespace,-runtime/printf:foo.h:14,+runtime/printf_format:foo.h - --filter=-,+build/include_what_you_use:foo.h:321 - - counting=total|toplevel|detailed - The total number of errors found is always printed. If - 'toplevel' is provided, then the count of errors in each of - the top-level categories like 'build' and 'whitespace' will - also be printed. If 'detailed' is provided, then a count - is provided for each category like 'legal/copyright'. - - repository=path - The top level directory of the repository, used to derive the header - guard CPP variable. By default, this is determined by searching for a - path that contains .git, .hg, or .svn. When this flag is specified, the - given path is used instead. This option allows the header guard CPP - variable to remain consistent even if members of a team have different - repository root directories (such as when checking out a subdirectory - with SVN). In addition, users of non-mainstream version control systems - can use this flag to ensure readable header guard CPP variables. - - Examples: - Assuming that Alice checks out ProjectName and Bob checks out - ProjectName/trunk and trunk contains src/chrome/ui/browser.h, then - with no --repository flag, the header guard CPP variable will be: - - Alice => TRUNK_SRC_CHROME_BROWSER_UI_BROWSER_H_ - Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - If Alice uses the --repository=trunk flag and Bob omits the flag or - uses --repository=. then the header guard CPP variable will be: - - Alice => SRC_CHROME_BROWSER_UI_BROWSER_H_ - Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - root=subdir - The root directory used for deriving header guard CPP variable. - This directory is relative to the top level directory of the repository - which by default is determined by searching for a directory that contains - .git, .hg, or .svn but can also be controlled with the --repository flag. - If the specified directory does not exist, this flag is ignored. - - Examples: - Assuming that src is the top level directory of the repository (and - cwd=top/src), the header guard CPP variables for - src/chrome/browser/ui/browser.h are: - - No flag => CHROME_BROWSER_UI_BROWSER_H_ - --root=chrome => BROWSER_UI_BROWSER_H_ - --root=chrome/browser => UI_BROWSER_H_ - --root=.. => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - linelength=digits - This is the allowed line length for the project. The default value is - 80 characters. - - Examples: - --linelength=120 - - recursive - Search for files to lint recursively. Each directory given in the list - of files to be linted is replaced by all files that descend from that - directory. Files with extensions not in the valid extensions list are - excluded. - - exclude=path - Exclude the given path from the list of files to be linted. Relative - paths are evaluated relative to the current directory and shell globbing - is performed. This flag can be provided multiple times to exclude - multiple files. - - Examples: - --exclude=one.cc - --exclude=src/*.cc - --exclude=src/*.cc --exclude=test/*.cc - - extensions=extension,extension,... - The allowed file extensions that cpplint will check - - Examples: - --extensions=%s - - includeorder=default|standardcfirst - For the build/include_order rule, the default is to blindly assume angle - bracket includes with file extension are c-system-headers (default), - even knowing this will have false classifications. - The default is established at google. - standardcfirst means to instead use an allow-list of known c headers and - treat all others as separate group of "other system headers". The C headers - included are those of the C-standard lib and closely related ones. - - config=filename - Search for config files with the specified name instead of CPPLINT.cfg - - headers=x,y,... - The header extensions that cpplint will treat as .h in checks. Values are - automatically added to --extensions list. - (by default, only files with extensions %s will be assumed to be headers) - - Examples: - --headers=%s - --headers=hpp,hxx - --headers=hpp - - cpplint.py supports per-directory configurations specified in CPPLINT.cfg - files. CPPLINT.cfg file can contain a number of key=value pairs. - Currently the following options are supported: - - set noparent - filter=+filter1,-filter2,... - exclude_files=regex - linelength=80 - root=subdir - headers=x,y,... - - "set noparent" option prevents cpplint from traversing directory tree - upwards looking for more .cfg files in parent directories. This option - is usually placed in the top-level project directory. - - The "filter" option is similar in function to --filter flag. It specifies - message filters in addition to the |_DEFAULT_FILTERS| and those specified - through --filter command-line flag. - - "exclude_files" allows to specify a regular expression to be matched against - a file name. If the expression matches, the file is skipped and not run - through the linter. - - "linelength" allows to specify the allowed line length for the project. - - The "root" option is similar in function to the --root flag (see example - above). Paths are relative to the directory of the CPPLINT.cfg. - - The "headers" option is similar in function to the --headers flag - (see example above). - - CPPLINT.cfg has an effect on files in the same directory and all - sub-directories, unless overridden by a nested configuration file. - - Example file: - filter=-build/include_order,+build/include_alpha - exclude_files=.*\\.cc - - The above example disables build/include_order warning and enables - build/include_alpha as well as excludes all .cc from being - processed by linter, in the current directory (where the .cfg - file is located) and all sub-directories. -""" - -# We categorize each error message we print. Here are the categories. -# We want an explicit list so we can list them all in cpplint --filter=. -# If you add a new error message with a new category, add it to the list -# here! cpplint_unittest.py should tell you if you forget to do this. -_ERROR_CATEGORIES = [ - "build/c++11", - "build/c++17", - "build/deprecated", - "build/endif_comment", - "build/explicit_make_pair", - "build/forward_decl", - "build/header_guard", - "build/include", - "build/include_subdir", - "build/include_alpha", - "build/include_order", - "build/include_what_you_use", - "build/namespaces_headers", - "build/namespaces_literals", - "build/namespaces", - "build/printf_format", - "build/storage_class", - "legal/copyright", - "readability/alt_tokens", - "readability/braces", - "readability/casting", - "readability/check", - "readability/constructors", - "readability/fn_size", - "readability/inheritance", - "readability/multiline_comment", - "readability/multiline_string", - "readability/namespace", - "readability/nolint", - "readability/nul", - "readability/todo", - "readability/utf8", - "runtime/arrays", - "runtime/casting", - "runtime/explicit", - "runtime/int", - "runtime/init", - "runtime/invalid_increment", - "runtime/member_string_references", - "runtime/memset", - "runtime/operator", - "runtime/printf", - "runtime/printf_format", - "runtime/references", - "runtime/string", - "runtime/threadsafe_fn", - "runtime/vlog", - "whitespace/blank_line", - "whitespace/braces", - "whitespace/comma", - "whitespace/comments", - "whitespace/empty_conditional_body", - "whitespace/empty_if_body", - "whitespace/empty_loop_body", - "whitespace/end_of_line", - "whitespace/ending_newline", - "whitespace/forcolon", - "whitespace/indent", - "whitespace/indent_namespace", - "whitespace/line_length", - "whitespace/newline", - "whitespace/operators", - "whitespace/parens", - "whitespace/semicolon", - "whitespace/tab", - "whitespace/todo", -] - -# keywords to use with --outputs which generate stdout for machine processing -_MACHINE_OUTPUTS = ["junit", "sed", "gsed"] - -# These error categories are no longer enforced by cpplint, but for backwards- -# compatibility they may still appear in NOLINT comments. -_LEGACY_ERROR_CATEGORIES = [ - "build/class", - "readability/streams", - "readability/function", -] - -# These prefixes for categories should be ignored since they relate to other -# tools which also use the NOLINT syntax, e.g. clang-tidy. -_OTHER_NOLINT_CATEGORY_PREFIXES = [ - "clang-analyzer-", - "abseil-", - "altera-", - "android-", - "boost-", - "bugprone-", - "cert-", - "concurrency-", - "cppcoreguidelines-", - "darwin-", - "fuchsia-", - "google-", - "hicpp-", - "linuxkernel-", - "llvm-", - "llvmlibc-", - "misc-", - "modernize-", - "mpi-", - "objc-", - "openmp-", - "performance-", - "portability-", - "readability-", - "zircon-", -] - -# The default state of the category filter. This is overridden by the --filter= -# flag. By default all errors are on, so only add here categories that should be -# off by default (i.e., categories that must be enabled by the --filter= flags). -# All entries here should start with a '-' or '+', as in the --filter= flag. -_DEFAULT_FILTERS = [ - "-build/include_alpha", - "-readability/fn_size", - "-runtime/references", -] - -# The default list of categories suppressed for C (not C++) files. -_DEFAULT_C_SUPPRESSED_CATEGORIES = [ - "readability/casting", -] - -# The default list of categories suppressed for Linux Kernel files. -_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ - "whitespace/tab", -] - -# We used to check for high-bit characters, but after much discussion we -# decided those were OK, as long as they were in UTF-8 and didn't represent -# hard-coded international strings, which belong in a separate i18n file. - -# C++ headers -_CPP_HEADERS = frozenset( - [ - # Legacy - "algobase.h", - "algo.h", - "alloc.h", - "builtinbuf.h", - "bvector.h", - # 'complex.h', collides with System C header "complex.h" since C11 - "defalloc.h", - "deque.h", - "editbuf.h", - "fstream.h", - "function.h", - "hash_map", - "hash_map.h", - "hash_set", - "hash_set.h", - "hashtable.h", - "heap.h", - "indstream.h", - "iomanip.h", - "iostream.h", - "istream.h", - "iterator.h", - "list.h", - "map.h", - "multimap.h", - "multiset.h", - "ostream.h", - "pair.h", - "parsestream.h", - "pfstream.h", - "procbuf.h", - "pthread_alloc", - "pthread_alloc.h", - "rope", - "rope.h", - "ropeimpl.h", - "set.h", - "slist", - "slist.h", - "stack.h", - "stdiostream.h", - "stl_alloc.h", - "stl_relops.h", - "streambuf.h", - "stream.h", - "strfile.h", - "strstream.h", - "tempbuf.h", - "tree.h", - "type_traits.h", - "vector.h", - # C++ library headers - "algorithm", - "array", - "atomic", - "bitset", - "chrono", - "codecvt", - "complex", - "condition_variable", - "deque", - "exception", - "forward_list", - "fstream", - "functional", - "future", - "initializer_list", - "iomanip", - "ios", - "iosfwd", - "iostream", - "istream", - "iterator", - "limits", - "list", - "locale", - "map", - "memory", - "mutex", - "new", - "numeric", - "ostream", - "queue", - "random", - "ratio", - "regex", - "scoped_allocator", - "set", - "sstream", - "stack", - "stdexcept", - "streambuf", - "string", - "strstream", - "system_error", - "thread", - "tuple", - "typeindex", - "typeinfo", - "type_traits", - "unordered_map", - "unordered_set", - "utility", - "valarray", - "vector", - # C++14 headers - "shared_mutex", - # C++17 headers - "any", - "charconv", - "codecvt", - "execution", - "filesystem", - "memory_resource", - "optional", - "string_view", - "variant", - # C++20 headers - "barrier", - "bit", - "compare", - "concepts", - "coroutine", - "format", - "latch", - "numbers", - "ranges", - "semaphore", - "source_location", - "span", - "stop_token", - "syncstream", - "version", - # C++23 headers - "expected", - "flat_map", - "flat_set", - "generator", - "mdspan", - "print", - "spanstream", - "stacktrace", - "stdfloat", - # C++ headers for C library facilities - "cassert", - "ccomplex", - "cctype", - "cerrno", - "cfenv", - "cfloat", - "cinttypes", - "ciso646", - "climits", - "clocale", - "cmath", - "csetjmp", - "csignal", - "cstdalign", - "cstdarg", - "cstdbool", - "cstddef", - "cstdint", - "cstdio", - "cstdlib", - "cstring", - "ctgmath", - "ctime", - "cuchar", - "cwchar", - "cwctype", - ] -) - -# C headers -_C_HEADERS = frozenset( - [ - # System C headers - "assert.h", - "complex.h", - "ctype.h", - "errno.h", - "fenv.h", - "float.h", - "inttypes.h", - "iso646.h", - "limits.h", - "locale.h", - "math.h", - "setjmp.h", - "signal.h", - "stdalign.h", - "stdarg.h", - "stdatomic.h", - "stdbool.h", - "stddef.h", - "stdint.h", - "stdio.h", - "stdlib.h", - "stdnoreturn.h", - "string.h", - "tgmath.h", - "threads.h", - "time.h", - "uchar.h", - "wchar.h", - "wctype.h", - # C23 headers - "stdbit.h", - "stdckdint.h", - # additional POSIX C headers - "aio.h", - "arpa/inet.h", - "cpio.h", - "dirent.h", - "dlfcn.h", - "fcntl.h", - "fmtmsg.h", - "fnmatch.h", - "ftw.h", - "glob.h", - "grp.h", - "iconv.h", - "langinfo.h", - "libgen.h", - "monetary.h", - "mqueue.h", - "ndbm.h", - "net/if.h", - "netdb.h", - "netinet/in.h", - "netinet/tcp.h", - "nl_types.h", - "poll.h", - "pthread.h", - "pwd.h", - "regex.h", - "sched.h", - "search.h", - "semaphore.h", - "setjmp.h", - "signal.h", - "spawn.h", - "strings.h", - "stropts.h", - "syslog.h", - "tar.h", - "termios.h", - "trace.h", - "ulimit.h", - "unistd.h", - "utime.h", - "utmpx.h", - "wordexp.h", - # additional GNUlib headers - "a.out.h", - "aliases.h", - "alloca.h", - "ar.h", - "argp.h", - "argz.h", - "byteswap.h", - "crypt.h", - "endian.h", - "envz.h", - "err.h", - "error.h", - "execinfo.h", - "fpu_control.h", - "fstab.h", - "fts.h", - "getopt.h", - "gshadow.h", - "ieee754.h", - "ifaddrs.h", - "libintl.h", - "mcheck.h", - "mntent.h", - "obstack.h", - "paths.h", - "printf.h", - "pty.h", - "resolv.h", - "shadow.h", - "sysexits.h", - "ttyent.h", - # Additional linux glibc headers - "dlfcn.h", - "elf.h", - "features.h", - "gconv.h", - "gnu-versions.h", - "lastlog.h", - "libio.h", - "link.h", - "malloc.h", - "memory.h", - "netash/ash.h", - "netatalk/at.h", - "netax25/ax25.h", - "neteconet/ec.h", - "netipx/ipx.h", - "netiucv/iucv.h", - "netpacket/packet.h", - "netrom/netrom.h", - "netrose/rose.h", - "nfs/nfs.h", - "nl_types.h", - "nss.h", - "re_comp.h", - "regexp.h", - "sched.h", - "sgtty.h", - "stab.h", - "stdc-predef.h", - "stdio_ext.h", - "syscall.h", - "termio.h", - "thread_db.h", - "ucontext.h", - "ustat.h", - "utmp.h", - "values.h", - "wait.h", - "xlocale.h", - # Hardware specific headers - "arm_neon.h", - "emmintrin.h", - "xmmintin.h", - ] -) - -# Folders of C libraries so commonly used in C++, -# that they have parity with standard C libraries. -C_STANDARD_HEADER_FOLDERS = frozenset( - [ - # standard C library - "sys", - # glibc for linux - "arpa", - "asm-generic", - "bits", - "gnu", - "net", - "netinet", - "protocols", - "rpc", - "rpcsvc", - "scsi", - # linux kernel header - "drm", - "linux", - "misc", - "mtd", - "rdma", - "sound", - "video", - "xen", - ] -) - -# Type names -_TYPES = re.compile( - r"^(?:" - # [dcl.type.simple] - r"(char(16_t|32_t)?)|wchar_t|" - r"bool|short|int|long|signed|unsigned|float|double|" - # [support.types] - r"(ptrdiff_t|size_t|max_align_t|nullptr_t)|" - # [cstdint.syn] - r"(u?int(_fast|_least)?(8|16|32|64)_t)|" - r"(u?int(max|ptr)_t)|" - r")$" -) - - -# These headers are excluded from [build/include] and [build/include_order] -# checks: -# - Anything not following google file name conventions (containing an -# uppercase character, such as Python.h or nsStringAPI.h, for example). -# - Lua headers. -_THIRD_PARTY_HEADERS_PATTERN = re.compile(r"^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$") - -# Pattern for matching FileInfo.BaseName() against test file name -_test_suffixes = ["_test", "_regtest", "_unittest"] -_TEST_FILE_SUFFIX = "(" + "|".join(_test_suffixes) + r")$" - -# Pattern that matches only complete whitespace, possibly across multiple lines. -_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r"^\s*$", re.DOTALL) - -# Assertion macros. These are defined in base/logging.h and -# testing/base/public/gunit.h. -_CHECK_MACROS = [ - "DCHECK", - "CHECK", - "EXPECT_TRUE", - "ASSERT_TRUE", - "EXPECT_FALSE", - "ASSERT_FALSE", -] - -# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE -_CHECK_REPLACEMENT: dict[str, dict[str, str]] = {macro_var: {} for macro_var in _CHECK_MACROS} - -for op, replacement in [ - ("==", "EQ"), - ("!=", "NE"), - (">=", "GE"), - (">", "GT"), - ("<=", "LE"), - ("<", "LT"), -]: - _CHECK_REPLACEMENT["DCHECK"][op] = f"DCHECK_{replacement}" - _CHECK_REPLACEMENT["CHECK"][op] = f"CHECK_{replacement}" - _CHECK_REPLACEMENT["EXPECT_TRUE"][op] = f"EXPECT_{replacement}" - _CHECK_REPLACEMENT["ASSERT_TRUE"][op] = f"ASSERT_{replacement}" - -for op, inv_replacement in [ - ("==", "NE"), - ("!=", "EQ"), - (">=", "LT"), - (">", "LE"), - ("<=", "GT"), - ("<", "GE"), -]: - _CHECK_REPLACEMENT["EXPECT_FALSE"][op] = f"EXPECT_{inv_replacement}" - _CHECK_REPLACEMENT["ASSERT_FALSE"][op] = f"ASSERT_{inv_replacement}" - -# Alternative tokens and their replacements. For full list, see section 2.5 -# Alternative tokens [lex.digraph] in the C++ standard. -# -# Digraphs (such as '%:') are not included here since it's a mess to -# match those on a word boundary. -_ALT_TOKEN_REPLACEMENT = { - "and": "&&", - "bitor": "|", - "or": "||", - "xor": "^", - "compl": "~", - "bitand": "&", - "and_eq": "&=", - "or_eq": "|=", - "xor_eq": "^=", - "not": "!", - "not_eq": "!=", -} - -# Compile regular expression that matches all the above keywords. The "[ =()]" -# bit is meant to avoid matching these keywords outside of boolean expressions. -# -# False positives include C-style multi-line comments and multi-line strings -# but those have always been troublesome for cpplint. -_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( - r"([ =()])(" + ("|".join(_ALT_TOKEN_REPLACEMENT.keys())) + r")([ (]|$)" -) - - -# These constants define types of headers for use with -# _IncludeState.CheckNextIncludeOrder(). -_C_SYS_HEADER = 1 -_CPP_SYS_HEADER = 2 -_OTHER_SYS_HEADER = 3 -_LIKELY_MY_HEADER = 4 -_POSSIBLE_MY_HEADER = 5 -_OTHER_HEADER = 6 - -# These constants define the current inline assembly state -_NO_ASM = 0 # Outside of inline assembly block -_INSIDE_ASM = 1 # Inside inline assembly block -_END_ASM = 2 # Last line of inline assembly block -_BLOCK_ASM = 3 # The whole block is an inline assembly block - -# Match start of assembly blocks -_MATCH_ASM = re.compile( - r"^\s*(?:asm|_asm|__asm|__asm__)" - r"(?:\s+(volatile|__volatile__))?" - r"\s*[{(]" -) - -# Match strings that indicate we're working on a C (not C++) file. -_SEARCH_C_FILE = re.compile( - r"\b(?:LINT_C_FILE|" - r"vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))" -) - -# Match string that indicates we're working on a Linux Kernel file. -_SEARCH_KERNEL_FILE = re.compile(r"\b(?:LINT_KERNEL_FILE)") - -# Commands for sed to fix the problem -_SED_FIXUPS = { - "Remove spaces around =": r"s/ = /=/", - "Remove spaces around !=": r"s/ != /!=/", - "Remove space before ( in if (": r"s/if (/if(/", - "Remove space before ( in for (": r"s/for (/for(/", - "Remove space before ( in while (": r"s/while (/while(/", - "Remove space before ( in switch (": r"s/switch (/switch(/", - "Should have a space between // and comment": r"s/\/\//\/\/ /", - "Missing space before {": r"s/\([^ ]\){/\1 {/", - "Tab found, replace by spaces": r"s/\t/ /g", - "Line ends in whitespace. Consider deleting these extra spaces.": r"s/\s*$//", - "You don't need a ; after a }": r"s/};/}/", - "Missing space after ,": r"s/,\([^ ]\)/, \1/g", -} - -# The root directory used for deriving header guard CPP variable. -# This is set by --root flag. -_root = None -_root_debug = False - -# The top level repository directory. If set, _root is calculated relative to -# this directory instead of the directory containing version control artifacts. -# This is set by the --repository flag. -_repository = None - -# Files to exclude from linting. This is set by the --exclude flag. -_excludes = None - -# Whether to suppress all PrintInfo messages, UNRELATED to --quiet flag -_quiet = False - -# The allowed line length of files. -# This is set by --linelength flag. -_line_length = 80 - -# This allows to use different include order rule than default -_include_order = "default" - -# This allows different config files to be used -_config_filename = "CPPLINT.cfg" - -# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. -# This is set by --headers flag. -_hpp_headers: set[str] = set() - - -class ErrorSuppressions: - """Class to track all error suppressions for cpplint""" - - class LineRange: - """Class to represent a range of line numbers for which an error is suppressed""" - - def __init__(self, begin, end): - self.begin = begin - self.end = end - - def __str__(self): - return f"[{self.begin}-{self.end}]" - - def __contains__(self, obj): - return self.begin <= obj <= self.end - - def ContainsRange(self, other): - return self.begin <= other.begin and self.end >= other.end - - def __init__(self): - self._suppressions = collections.defaultdict(list) - self._open_block_suppression = None - - def _AddSuppression(self, category, line_range): - suppressed = self._suppressions[category] - if not (suppressed and suppressed[-1].ContainsRange(line_range)): - suppressed.append(line_range) - - def GetOpenBlockStart(self): - """:return: The start of the current open block or `-1` if there is not an open block""" - return self._open_block_suppression.begin if self._open_block_suppression else -1 - - def AddGlobalSuppression(self, category): - """Add a suppression for `category` which is suppressed for the whole file""" - self._AddSuppression(category, self.LineRange(0, math.inf)) - - def AddLineSuppression(self, category, linenum): - """Add a suppression for `category` which is suppressed only on `linenum`""" - self._AddSuppression(category, self.LineRange(linenum, linenum)) - - def StartBlockSuppression(self, category, linenum): - """Start a suppression block for `category` on `linenum`. inclusive""" - if self._open_block_suppression is None: - self._open_block_suppression = self.LineRange(linenum, math.inf) - self._AddSuppression(category, self._open_block_suppression) - - def EndBlockSuppression(self, linenum): - """End the current block suppression on `linenum`. inclusive""" - if self._open_block_suppression: - self._open_block_suppression.end = linenum - self._open_block_suppression = None - - def IsSuppressed(self, category, linenum): - """:return: `True` if `category` is suppressed for `linenum`""" - suppressed = self._suppressions[category] + self._suppressions[None] - return any(linenum in lr for lr in suppressed) - - def HasOpenBlock(self): - """:return: `True` if a block suppression was started but not ended""" - return self._open_block_suppression is not None - - def Clear(self): - """Clear all current error suppressions""" - self._suppressions.clear() - self._open_block_suppression = None - - -# {str, set(int)}: a map from error categories to sets of linenumbers -# on which those errors are expected and should be suppressed. -_error_suppressions = ErrorSuppressions() - - -def ProcessHppHeadersOption(val): - global _hpp_headers - try: - _hpp_headers = {ext.strip() for ext in val.split(",")} - except ValueError: - PrintUsage("Header extensions must be comma separated list.") - - -def ProcessIncludeOrderOption(val): - if val is None or val == "default": - pass - elif val == "standardcfirst": - global _include_order - _include_order = val - else: - PrintUsage("Invalid includeorder value %s. Expected default|standardcfirst") - - -def IsHeaderExtension(file_extension): - return file_extension in GetHeaderExtensions() - - -def GetHeaderExtensions(): - if _hpp_headers: - return _hpp_headers - if _valid_extensions: - return {h for h in _valid_extensions if "h" in h} - return {"h", "hh", "hpp", "hxx", "h++", "cuh"} - - -# The allowed extensions for file names -# This is set by --extensions flag -def GetAllExtensions(): - return GetHeaderExtensions().union(_valid_extensions or {"c", "cc", "cpp", "cxx", "c++", "cu"}) - - -def ProcessExtensionsOption(val): - global _valid_extensions - try: - extensions = [ext.strip() for ext in val.split(",")] - _valid_extensions = set(extensions) - except ValueError: - PrintUsage( - "Extensions should be a comma-separated list of values;" - "for example: extensions=hpp,cpp\n" - f'This could not be parsed: "{val}"' - ) - - -def GetNonHeaderExtensions(): - return GetAllExtensions().difference(GetHeaderExtensions()) - - -def ParseNolintSuppressions(filename, raw_line, linenum, error): - """Updates the global list of line error-suppressions. - - Parses any NOLINT comments on the current line, updating the global - error_suppressions store. Reports an error if the NOLINT comment - was malformed. - - Args: - filename: str, the name of the input file. - raw_line: str, the line of input text, with comments. - linenum: int, the number of the current line. - error: function, an error handler. - """ - if matched := re.search(r"\bNOLINT(NEXTLINE|BEGIN|END)?\b(\([^)]+\))?", raw_line): - no_lint_type = matched.group(1) - if no_lint_type == "NEXTLINE": - - def ProcessCategory(category): - _error_suppressions.AddLineSuppression(category, linenum + 1) - elif no_lint_type == "BEGIN": - if _error_suppressions.HasOpenBlock(): - error( - filename, - linenum, - "readability/nolint", - 5, - ( - "NONLINT block already defined on line " - f"{_error_suppressions.GetOpenBlockStart()}" - ), - ) - - def ProcessCategory(category): - _error_suppressions.StartBlockSuppression(category, linenum) - elif no_lint_type == "END": - if not _error_suppressions.HasOpenBlock(): - error(filename, linenum, "readability/nolint", 5, "Not in a NOLINT block") - - def ProcessCategory(category): - if category is not None: - error( - filename, - linenum, - "readability/nolint", - 5, - f"NOLINT categories not supported in block END: {category}", - ) - _error_suppressions.EndBlockSuppression(linenum) - else: - - def ProcessCategory(category): - _error_suppressions.AddLineSuppression(category, linenum) - - categories = matched.group(2) - if categories in (None, "(*)"): # => "suppress all" - ProcessCategory(None) - elif categories.startswith("(") and categories.endswith(")"): - for category in {c.strip() for c in categories[1:-1].split(",")}: - if category in _ERROR_CATEGORIES: - ProcessCategory(category) - elif any(c for c in _OTHER_NOLINT_CATEGORY_PREFIXES if category.startswith(c)): - # Ignore any categories from other tools. - pass - elif category not in _LEGACY_ERROR_CATEGORIES: - error( - filename, - linenum, - "readability/nolint", - 5, - f"Unknown NOLINT error category: {category}", - ) - - -def ProcessGlobalSuppressions(filename: str, lines: list[str]) -> None: - """Updates the list of global error suppressions. - - Parses any lint directives in the file that have global effect. - - Args: - lines: An array of strings, each representing a line of the file, with the - last element being empty if the file is terminated with a newline. - filename: str, the name of the input file. - """ - for line in lines: - if _SEARCH_C_FILE.search(line) or filename.lower().endswith((".c", ".cu")): - for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: - _error_suppressions.AddGlobalSuppression(category) - if _SEARCH_KERNEL_FILE.search(line): - for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: - _error_suppressions.AddGlobalSuppression(category) - - -def ResetNolintSuppressions(): - """Resets the set of NOLINT suppressions to empty.""" - _error_suppressions.Clear() - - -def IsErrorSuppressedByNolint(category, linenum): - """Returns true if the specified error category is suppressed on this line. - - Consults the global error_suppressions map populated by - ParseNolintSuppressions/ProcessGlobalSuppressions/ResetNolintSuppressions. - - Args: - category: str, the category of the error. - linenum: int, the current line number. - Returns: - bool, True iff the error should be suppressed due to a NOLINT comment, - block suppression or global suppression. - """ - return _error_suppressions.IsSuppressed(category, linenum) - - -def _IsSourceExtension(s): - """File extension (excluding dot) matches a source file extension.""" - return s in GetNonHeaderExtensions() - - -class _IncludeState: - """Tracks line numbers for includes, and the order in which includes appear. - - include_list contains list of lists of (header, line number) pairs. - It's a lists of lists rather than just one flat list to make it - easier to update across preprocessor boundaries. - - Call CheckNextIncludeOrder() once for each header in the file, passing - in the type constants defined above. Calls in an illegal order will - raise an _IncludeError with an appropriate error message. - - """ - - # self._section will move monotonically through this set. If it ever - # needs to move backwards, CheckNextIncludeOrder will raise an error. - _INITIAL_SECTION = 0 - _MY_H_SECTION = 1 - _C_SECTION = 2 - _CPP_SECTION = 3 - _OTHER_SYS_SECTION = 4 - _OTHER_H_SECTION = 5 - - _TYPE_NAMES = { - _C_SYS_HEADER: "C system header", - _CPP_SYS_HEADER: "C++ system header", - _OTHER_SYS_HEADER: "other system header", - _LIKELY_MY_HEADER: "header this file implements", - _POSSIBLE_MY_HEADER: "header this file may implement", - _OTHER_HEADER: "other header", - } - _SECTION_NAMES = { - _INITIAL_SECTION: "... nothing. (This can't be an error.)", - _MY_H_SECTION: "a header this file implements", - _C_SECTION: "C system header", - _CPP_SECTION: "C++ system header", - _OTHER_SYS_SECTION: "other system header", - _OTHER_H_SECTION: "other header", - } - - def __init__(self): - self.include_list = [[]] - self._section = None - self._last_header = None - self.ResetSection("") - - def FindHeader(self, header): - """Check if a header has already been included. - - Args: - header: header to check. - Returns: - Line number of previous occurrence, or -1 if the header has not - been seen before. - """ - for section_list in self.include_list: - for f in section_list: - if f[0] == header: - return f[1] - return -1 - - def ResetSection(self, directive): - """Reset section checking for preprocessor directive. - - Args: - directive: preprocessor directive (e.g. "if", "else"). - """ - # The name of the current section. - self._section = self._INITIAL_SECTION - # The path of last found header. - self._last_header = "" - - # Update list of includes. Note that we never pop from the - # include list. - if directive in ("if", "ifdef", "ifndef"): - self.include_list.append([]) - elif directive in ("else", "elif"): - self.include_list[-1] = [] - - def SetLastHeader(self, header_path): - self._last_header = header_path - - def CanonicalizeAlphabeticalOrder(self, header_path): - """Returns a path canonicalized for alphabetical comparison. - - - replaces "-" with "_" so they both cmp the same. - - removes '-inl' since we don't require them to be after the main header. - - lowercase everything, just in case. - - Args: - header_path: Path to be canonicalized. - - Returns: - Canonicalized path. - """ - return header_path.replace("-inl.h", ".h").replace("-", "_").lower() - - def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): - """Check if a header is in alphabetical order with the previous header. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - header_path: Canonicalized header to be checked. - - Returns: - Returns true if the header is in alphabetical order. - """ - # If previous section is different from current section, _last_header will - # be reset to empty string, so it's always less than current header. - # - # If previous line was a blank line, assume that the headers are - # intentionally sorted the way they are. - return not ( - self._last_header > header_path - and re.match(r"^\s*#\s*include\b", clean_lines.elided[linenum - 1]) - ) - - def CheckNextIncludeOrder(self, header_type): - """Returns a non-empty error message if the next header is out of order. - - This function also updates the internal state to be ready to check - the next include. - - Args: - header_type: One of the _XXX_HEADER constants defined above. - - Returns: - The empty string if the header is in the right order, or an - error message describing what's wrong. - - """ - error_message = ( - f"Found {self._TYPE_NAMES[header_type]} after {self._SECTION_NAMES[self._section]}" - ) - - last_section = self._section - - if header_type == _C_SYS_HEADER: - if self._section <= self._C_SECTION: - self._section = self._C_SECTION - else: - self._last_header = "" - return error_message - elif header_type == _CPP_SYS_HEADER: - if self._section <= self._CPP_SECTION: - self._section = self._CPP_SECTION - else: - self._last_header = "" - return error_message - elif header_type == _OTHER_SYS_HEADER: - if self._section <= self._OTHER_SYS_SECTION: - self._section = self._OTHER_SYS_SECTION - else: - self._last_header = "" - return error_message - elif header_type == _LIKELY_MY_HEADER: - if self._section <= self._MY_H_SECTION: - self._section = self._MY_H_SECTION - else: - self._section = self._OTHER_H_SECTION - elif header_type == _POSSIBLE_MY_HEADER: - if self._section <= self._MY_H_SECTION: - self._section = self._MY_H_SECTION - else: - # This will always be the fallback because we're not sure - # enough that the header is associated with this file. - self._section = self._OTHER_H_SECTION - else: - assert header_type == _OTHER_HEADER - self._section = self._OTHER_H_SECTION - - if last_section != self._section: - self._last_header = "" - - return "" - - -class _CppLintState: - """Maintains module-wide state..""" - - def __init__(self): - self.verbose_level = 1 # global setting. - self.error_count = 0 # global count of reported errors - # filters to apply when emitting error messages - self.filters = _DEFAULT_FILTERS[:] - # backup of filter list. Used to restore the state after each file. - self._filters_backup = self.filters[:] - self.counting = "total" # In what way are we counting errors? - self.errors_by_category = {} # string to int dict storing error counts - self.quiet = False # Suppress non-error messages? - - # output format: - # "emacs" - format that emacs can parse (default) - # "eclipse" - format that eclipse can parse - # "vs7" - format that Microsoft Visual Studio 7 can parse - # "junit" - format that Jenkins, Bamboo, etc can parse - # "sed" - returns a gnu sed command to fix the problem - # "gsed" - like sed, but names the command gsed, e.g. for macOS homebrew users - self.output_format = "emacs" - - # For JUnit output, save errors and failures until the end so that they - # can be written into the XML - self._junit_errors = [] - self._junit_failures = [] - - def SetOutputFormat(self, output_format): - """Sets the output format for errors.""" - self.output_format = output_format - - def SetQuiet(self, quiet): - """Sets the module's quiet settings, and returns the previous setting.""" - last_quiet = self.quiet - self.quiet = quiet - return last_quiet - - def SetVerboseLevel(self, level): - """Sets the module's verbosity, and returns the previous setting.""" - last_verbose_level = self.verbose_level - self.verbose_level = level - return last_verbose_level - - def SetCountingStyle(self, counting_style): - """Sets the module's counting options.""" - self.counting = counting_style - - def SetFilters(self, filters): - """Sets the error-message filters. - - These filters are applied when deciding whether to emit a given - error message. - - Args: - filters: A string of comma-separated filters (eg "+whitespace/indent"). - Each filter should start with + or -; else we die. - - Raises: - ValueError: The comma-separated filters did not all start with '+' or '-'. - E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" - """ - # Default filters always have less priority than the flag ones. - self.filters = _DEFAULT_FILTERS[:] - self.AddFilters(filters) - - def AddFilters(self, filters): - """Adds more filters to the existing list of error-message filters.""" - for filt in filters.split(","): - clean_filt = filt.strip() - if clean_filt: - self.filters.append(clean_filt) - for filt in self.filters: - if not filt.startswith(("+", "-")): - msg = f"Every filter in --filters must start with + or - ({filt} does not)" - raise ValueError(msg) - - def BackupFilters(self): - """Saves the current filter list to backup storage.""" - self._filters_backup = self.filters[:] - - def RestoreFilters(self): - """Restores filters previously backed up.""" - self.filters = self._filters_backup[:] - - def ResetErrorCounts(self): - """Sets the module's error statistic back to zero.""" - self.error_count = 0 - self.errors_by_category = {} - - def IncrementErrorCount(self, category): - """Bumps the module's error statistic.""" - self.error_count += 1 - if self.counting in ("toplevel", "detailed"): - if self.counting != "detailed": - category = category.split("/")[0] - if category not in self.errors_by_category: - self.errors_by_category[category] = 0 - self.errors_by_category[category] += 1 - - def PrintErrorCounts(self): - """Print a summary of errors by category, and the total.""" - for category, count in sorted(dict.items(self.errors_by_category)): - self.PrintInfo(f"Category '{category}' errors found: {count}\n") - if self.error_count > 0: - self.PrintInfo(f"Total errors found: {self.error_count}\n") - - def PrintInfo(self, message): - # _quiet does not represent --quiet flag. - # Hide infos from stdout to keep stdout pure for machine consumption - if not _quiet and self.output_format not in _MACHINE_OUTPUTS: - sys.stdout.write(message) - - def PrintError(self, message): - if self.output_format == "junit": - self._junit_errors.append(message) - else: - sys.stderr.write(message) - - def AddJUnitFailure(self, filename, linenum, message, category, confidence): - self._junit_failures.append((filename, linenum, message, category, confidence)) - - def FormatJUnitXML(self): - num_errors = len(self._junit_errors) - num_failures = len(self._junit_failures) - - testsuite = xml.etree.ElementTree.Element("testsuite") - testsuite.attrib["errors"] = str(num_errors) - testsuite.attrib["failures"] = str(num_failures) - testsuite.attrib["name"] = "cpplint" - - if num_errors == 0 and num_failures == 0: - testsuite.attrib["tests"] = str(1) - xml.etree.ElementTree.SubElement(testsuite, "testcase", name="passed") - - else: - testsuite.attrib["tests"] = str(num_errors + num_failures) - if num_errors > 0: - testcase = xml.etree.ElementTree.SubElement(testsuite, "testcase") - testcase.attrib["name"] = "errors" - error = xml.etree.ElementTree.SubElement(testcase, "error") - error.text = "\n".join(self._junit_errors) - if num_failures > 0: - # Group failures by file - failed_file_order = [] - failures_by_file = {} - for failure in self._junit_failures: - failed_file = failure[0] - if failed_file not in failed_file_order: - failed_file_order.append(failed_file) - failures_by_file[failed_file] = [] - failures_by_file[failed_file].append(failure) - # Create a testcase for each file - for failed_file in failed_file_order: - failures = failures_by_file[failed_file] - testcase = xml.etree.ElementTree.SubElement(testsuite, "testcase") - testcase.attrib["name"] = failed_file - failure = xml.etree.ElementTree.SubElement(testcase, "failure") - template = "{0}: {1} [{2}] [{3}]" - texts = [template.format(f[1], f[2], f[3], f[4]) for f in failures] - failure.text = "\n".join(texts) - - xml_decl = '\n' - return xml_decl + xml.etree.ElementTree.tostring(testsuite, "utf-8").decode("utf-8") - - -_cpplint_state = _CppLintState() - - -def _OutputFormat(): - """Gets the module's output format.""" - return _cpplint_state.output_format - - -def _SetOutputFormat(output_format): - """Sets the module's output format.""" - _cpplint_state.SetOutputFormat(output_format) - - -def _Quiet(): - """Return's the module's quiet setting.""" - return _cpplint_state.quiet - - -def _SetQuiet(quiet): - """Set the module's quiet status, and return previous setting.""" - return _cpplint_state.SetQuiet(quiet) - - -def _VerboseLevel(): - """Returns the module's verbosity setting.""" - return _cpplint_state.verbose_level - - -def _SetVerboseLevel(level): - """Sets the module's verbosity, and returns the previous setting.""" - return _cpplint_state.SetVerboseLevel(level) - - -def _SetCountingStyle(level): - """Sets the module's counting options.""" - _cpplint_state.SetCountingStyle(level) - - -def _Filters(): - """Returns the module's list of output filters, as a list.""" - return _cpplint_state.filters - - -def _SetFilters(filters): - """Sets the module's error-message filters. - - These filters are applied when deciding whether to emit a given - error message. - - Args: - filters: A string of comma-separated filters (eg "whitespace/indent"). - Each filter should start with + or -; else we die. - """ - _cpplint_state.SetFilters(filters) - - -def _AddFilters(filters): - """Adds more filter overrides. - - Unlike _SetFilters, this function does not reset the current list of filters - available. - - Args: - filters: A string of comma-separated filters (eg "whitespace/indent"). - Each filter should start with + or -; else we die. - """ - _cpplint_state.AddFilters(filters) - - -def _BackupFilters(): - """Saves the current filter list to backup storage.""" - _cpplint_state.BackupFilters() - - -def _RestoreFilters(): - """Restores filters previously backed up.""" - _cpplint_state.RestoreFilters() - - -class _FunctionState: - """Tracks current function name and the number of lines in its body.""" - - _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. - _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. - - def __init__(self): - self.in_a_function = False - self.lines_in_function = 0 - self.current_function = "" - - def Begin(self, function_name): - """Start analyzing function body. - - Args: - function_name: The name of the function being tracked. - """ - self.in_a_function = True - self.lines_in_function = 0 - self.current_function = function_name - - def Count(self): - """Count line in current function body.""" - if self.in_a_function: - self.lines_in_function += 1 - - def Check(self, error, filename, linenum): - """Report if too many lines in function body. - - Args: - error: The function to call with any errors found. - filename: The name of the current file. - linenum: The number of the line to check. - """ - if not self.in_a_function: - return - - if re.match(r"T(EST|est)", self.current_function): - base_trigger = self._TEST_TRIGGER - else: - base_trigger = self._NORMAL_TRIGGER - trigger = base_trigger * 2 ** _VerboseLevel() - - if self.lines_in_function > trigger: - error_level = int(math.log2(self.lines_in_function / base_trigger)) - # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... - error_level = min(error_level, 5) - error( - filename, - linenum, - "readability/fn_size", - error_level, - "Small and focused functions are preferred:" - f" {self.current_function} has {self.lines_in_function} non-comment lines" - f" (error triggered by exceeding {trigger} lines).", - ) - - def End(self): - """Stop analyzing function body.""" - self.in_a_function = False - - -class _IncludeError(Exception): - """Indicates a problem with the include order in a file.""" - - pass - - -class FileInfo: - """Provides utility functions for filenames. - - FileInfo provides easy access to the components of a file's path - relative to the project root. - """ - - def __init__(self, filename): - self._filename = filename - - def FullName(self): - """Make Windows paths like Unix.""" - return os.path.abspath(self._filename).replace("\\", "/") - - def RepositoryName(self): - r"""FullName after removing the local path to the repository. - - If we have a real absolute path name here we can try to do something smart: - detecting the root of the checkout and truncating /path/to/checkout from - the name so that we get header guards that don't include things like - "C:\\Documents and Settings\\..." or "/home/username/..." in them and thus - people on different computers who have checked the source out to different - locations won't see bogus errors. - """ - fullname = self.FullName() - - if os.path.exists(fullname): - project_dir = os.path.dirname(fullname) - - # If the user specified a repository path, it exists, and the file is - # contained in it, use the specified repository path - if _repository: - repo = FileInfo(_repository).FullName() - root_dir = project_dir - while os.path.exists(root_dir): - # allow case insensitive compare on Windows - if os.path.normcase(root_dir) == os.path.normcase(repo): - return os.path.relpath(fullname, root_dir).replace("\\", "/") - one_up_dir = os.path.dirname(root_dir) - if one_up_dir == root_dir: - break - root_dir = one_up_dir - - if os.path.exists(os.path.join(project_dir, ".svn")): - # If there's a .svn file in the current directory, we recursively look - # up the directory tree for the top of the SVN checkout - root_dir = project_dir - one_up_dir = os.path.dirname(root_dir) - while os.path.exists(os.path.join(one_up_dir, ".svn")): - root_dir = os.path.dirname(root_dir) - one_up_dir = os.path.dirname(one_up_dir) - - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1 :] - - # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by - # searching up from the current path. - root_dir = current_dir = os.path.dirname(fullname) - while current_dir != os.path.dirname(current_dir): - if ( - os.path.exists(os.path.join(current_dir, ".git")) - or os.path.exists(os.path.join(current_dir, ".hg")) - or os.path.exists(os.path.join(current_dir, ".svn")) - ): - root_dir = current_dir - break - current_dir = os.path.dirname(current_dir) - - if ( - os.path.exists(os.path.join(root_dir, ".git")) - or os.path.exists(os.path.join(root_dir, ".hg")) - or os.path.exists(os.path.join(root_dir, ".svn")) - ): - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1 :] - - # Don't know what to do; header guard warnings may be wrong... - return fullname - - def Split(self): - """Splits the file into the directory, basename, and extension. - - For 'chrome/browser/browser.cc', Split() would - return ('chrome/browser', 'browser', '.cc') - - Returns: - A tuple of (directory, basename, extension). - """ - - googlename = self.RepositoryName() - project, rest = os.path.split(googlename) - return (project,) + os.path.splitext(rest) - - def BaseName(self): - """File base name - text after the final slash, before the final period.""" - return self.Split()[1] - - def Extension(self): - """File extension - text following the final period, includes that period.""" - return self.Split()[2] - - def NoExtension(self): - """File has no source file extension.""" - return "/".join(self.Split()[0:2]) - - def IsSource(self): - """File has a source file extension.""" - return _IsSourceExtension(self.Extension()[1:]) - - -def _ShouldPrintError(category, confidence, filename, linenum): - """If confidence >= verbose, category passes filter and is not suppressed.""" - - # There are three ways we might decide not to print an error message: - # a "NOLINT(category)" comment appears in the source, - # the verbosity level isn't high enough, or the filters filter it out. - if IsErrorSuppressedByNolint(category, linenum): - return False - - if confidence < _cpplint_state.verbose_level: - return False - - is_filtered = False - for one_filter in _Filters(): - filter_cat, filter_file, filter_line = _ParseFilterSelector(one_filter[1:]) - category_match = category.startswith(filter_cat) - file_match = filter_file in ("", filename) - line_match = filter_line in (linenum, -1) - - if one_filter.startswith("-"): - if category_match and file_match and line_match: - is_filtered = True - elif one_filter.startswith("+"): - if category_match and file_match and line_match: - is_filtered = False - else: - # should have been checked for in SetFilter. - msg = f"Invalid filter: {one_filter}" - raise ValueError(msg) - return not is_filtered - - -def Error(filename, linenum, category, confidence, message): - """Logs the fact we've found a lint error. - - We log where the error was found, and also our confidence in the error, - that is, how certain we are this is a legitimate style regression, and - not a misidentification or a use that's sometimes justified. - - False positives can be suppressed by the use of "NOLINT(category)" - comments, NOLINTNEXTLINE or in blocks started by NOLINTBEGIN. These - are parsed into _error_suppressions. - - Args: - filename: The name of the file containing the error. - linenum: The number of the line containing the error. - category: A string used to describe the "category" this bug - falls under: "whitespace", say, or "runtime". Categories - may have a hierarchy separated by slashes: "whitespace/indent". - confidence: A number from 1-5 representing a confidence score for - the error, with 5 meaning that we are certain of the problem, - and 1 meaning that it could be a legitimate construct. - message: The error message. - """ - if _ShouldPrintError(category, confidence, filename, linenum): - _cpplint_state.IncrementErrorCount(category) - if _cpplint_state.output_format == "vs7": - _cpplint_state.PrintError( - f"{filename}({linenum}): error cpplint: [{category}] {message} [{confidence}]\n" - ) - elif _cpplint_state.output_format == "eclipse": - sys.stderr.write( - f"{filename}:{linenum}: warning: {message} [{category}] [{confidence}]\n" - ) - elif _cpplint_state.output_format == "junit": - _cpplint_state.AddJUnitFailure(filename, linenum, message, category, confidence) - elif _cpplint_state.output_format in ["sed", "gsed"]: - if message in _SED_FIXUPS: - sys.stdout.write( - f"{_cpplint_state.output_format} -i" - f" '{linenum}{_SED_FIXUPS[message]}' {filename}" - f" # {message} [{category}] [{confidence}]\n" - ) - else: - sys.stderr.write( - f'# {filename}:{linenum}: "{message}" [{category}] [{confidence}]\n' - ) - else: - final_message = f"{filename}:{linenum}: {message} [{category}] [{confidence}]\n" - sys.stderr.write(final_message) - - -# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. -_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') -# Match a single C style comment on the same line. -_RE_PATTERN_C_COMMENTS = r"/\*(?:[^*]|\*(?!/))*\*/" -# Matches multi-line C style comments. -# This RE is a little bit more complicated than one might expect, because we -# have to take care of space removals tools so we can handle comments inside -# statements better. -# The current rule is: We only clear spaces from both sides when we're at the -# end of the line. Otherwise, we try to remove spaces from the right side, -# if this doesn't work we try on left side but only if there's a non-character -# on the right. -_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( - r"(\s*" - + _RE_PATTERN_C_COMMENTS - + r"\s*$|" - + _RE_PATTERN_C_COMMENTS - + r"\s+|" - + r"\s+" - + _RE_PATTERN_C_COMMENTS - + r"(?=\W)|" - + _RE_PATTERN_C_COMMENTS - + r")" -) - - -def IsCppString(line): - """Does line terminate so, that the next symbol is in string constant. - - This function does not consider single-line nor multi-line comments. - - Args: - line: is a partial line of code starting from the 0..n. - - Returns: - True, if next character appended to 'line' is inside a - string constant. - """ - - line = line.replace(r"\\", "XX") # after this, \\" does not match to \" - return ((line.count('"') - line.count(r"\"") - line.count("'\"'")) & 1) == 1 - - -def CleanseRawStrings(raw_lines): - """Removes C++11 raw strings from lines. - - Before: - static const char kData[] = R"( - multi-line string - )"; - - After: - static const char kData[] = "" - (replaced by blank line) - ""; - - Args: - raw_lines: list of raw lines. - - Returns: - list of lines with C++11 raw strings replaced by empty strings. - """ - - delimiter = None - lines_without_raw_strings = [] - for line in raw_lines: - if delimiter: - # Inside a raw string, look for the end - end = line.find(delimiter) - if end >= 0: - # Found the end of the string, match leading space for this - # line and resume copying the original lines, and also insert - # a "" on the last line. - leading_space = re.match(r"^(\s*)\S", line) - line = leading_space.group(1) + '""' + line[end + len(delimiter) :] - delimiter = None - else: - # Haven't found the end yet, append a blank line. - line = '""' - - # Look for beginning of a raw string, and replace them with - # empty strings. This is done in a loop to handle multiple raw - # strings on the same line. - while delimiter is None: - # Look for beginning of a raw string. - # See 2.14.15 [lex.string] for syntax. - # - # Once we have matched a raw string, we check the prefix of the - # line to make sure that the line is not part of a single line - # comment. It's done this way because we remove raw strings - # before removing comments as opposed to removing comments - # before removing raw strings. This is because there are some - # cpplint checks that requires the comments to be preserved, but - # we don't want to check comments that are inside raw strings. - matched = re.match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) - if matched and not re.match( - r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', matched.group(1) - ): - delimiter = ")" + matched.group(2) + '"' - - end = matched.group(3).find(delimiter) - if end >= 0: - # Raw string ended on same line - line = matched.group(1) + '""' + matched.group(3)[end + len(delimiter) :] - delimiter = None - else: - # Start of a multi-line raw string - line = matched.group(1) + '""' - else: - break - - lines_without_raw_strings.append(line) - - # TODO(google): if delimiter is not None here, we might want to - # emit a warning for unterminated string. - return lines_without_raw_strings - - -def FindNextMultiLineCommentStart(lines, lineix): - """Find the beginning marker for a multiline comment.""" - while lineix < len(lines): - if lines[lineix].strip().startswith("/*"): - # Only return this marker if the comment goes beyond this line - if lines[lineix].strip().find("*/", 2) < 0: - return lineix - lineix += 1 - return len(lines) - - -def FindNextMultiLineCommentEnd(lines, lineix): - """We are inside a comment, find the end marker.""" - while lineix < len(lines): - if lines[lineix].strip().endswith("*/"): - return lineix - lineix += 1 - return len(lines) - - -def RemoveMultiLineCommentsFromRange(lines, begin, end): - """Clears a range of lines for multi-line comments.""" - # Having // comments makes the lines non-empty, so we will not get - # unnecessary blank line warnings later in the code. - for i in range(begin, end): - lines[i] = "/**/" - - -def RemoveMultiLineComments(filename, lines, error): - """Removes multiline (c-style) comments from lines.""" - lineix = 0 - while lineix < len(lines): - lineix_begin = FindNextMultiLineCommentStart(lines, lineix) - if lineix_begin >= len(lines): - return - lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) - if lineix_end >= len(lines): - error( - filename, - lineix_begin + 1, - "readability/multiline_comment", - 5, - "Could not find end of multi-line comment", - ) - return - RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) - lineix = lineix_end + 1 - - -def CleanseComments(line): - """Removes //-comments and single-line C-style /* */ comments. - - Args: - line: A line of C++ source. - - Returns: - The line with single-line comments removed. - """ - commentpos = line.find("//") - if commentpos != -1 and not IsCppString(line[:commentpos]): - line = line[:commentpos].rstrip() - # get rid of /* ... */ - return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub("", line) - - -def ReplaceAlternateTokens(line): - """Replace any alternate token by its original counterpart. - - In order to comply with the google rule stating that unary operators should - never be followed by a space, an exception is made for the 'not' and 'compl' - alternate tokens. For these, any trailing space is removed during the - conversion. - - Args: - line: The line being processed. - - Returns: - The line with alternate tokens replaced. - """ - for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): - token = _ALT_TOKEN_REPLACEMENT[match.group(2)] - tail = "" if match.group(2) in ["not", "compl"] and match.group(3) == " " else r"\3" - line = re.sub(match.re, rf"\1{token}{tail}", line, count=1) - return line - - -class CleansedLines: - """Holds 4 copies of all lines with different preprocessing applied to them. - - 1) elided member contains lines without strings and comments. - 2) lines member contains lines without comments. - 3) raw_lines member contains all the lines without processing. - 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw - strings removed. - All these members are of , and of the same length. - """ - - def __init__(self, lines): - if "-readability/alt_tokens" in _cpplint_state.filters: - for i, line in enumerate(lines): - lines[i] = ReplaceAlternateTokens(line) - self.elided = [] - self.lines = [] - self.raw_lines = lines - self.num_lines = len(lines) - self.lines_without_raw_strings = CleanseRawStrings(lines) - for line in self.lines_without_raw_strings: - self.lines.append(CleanseComments(line)) - elided = self._CollapseStrings(line) - self.elided.append(CleanseComments(elided)) - - def NumLines(self): - """Returns the number of lines represented.""" - return self.num_lines - - @staticmethod - def _CollapseStrings(elided): - """Collapses strings and chars on a line to simple "" or '' blocks. - - We nix strings first so we're not fooled by text like '"http://"' - - Args: - elided: The line being processed. - - Returns: - The line with collapsed strings. - """ - if _RE_PATTERN_INCLUDE.match(elided): - return elided - - # Remove escaped characters first to make quote/single quote collapsing - # basic. Things that look like escaped characters shouldn't occur - # outside of strings and chars. - elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub("", elided) - - # Replace quoted strings and digit separators. Both single quotes - # and double quotes are processed in the same loop, otherwise - # nested quotes wouldn't work. - collapsed = "" - while True: - # Find the first quote character - match = re.match(r'^([^\'"]*)([\'"])(.*)$', elided) - if not match: - collapsed += elided - break - head, quote, tail = match.groups() - - if quote == '"': - # Collapse double quoted strings - second_quote = tail.find('"') - if second_quote >= 0: - collapsed += head + '""' - elided = tail[second_quote + 1 :] - else: - # Unmatched double quote, don't bother processing the rest - # of the line since this is probably a multiline string. - collapsed += elided - break - else: - # Found single quote, check nearby text to eliminate digit separators. - # - # There is no special handling for floating point here, because - # the integer/fractional/exponent parts would all be parsed - # correctly as long as there are digits on both sides of the - # separator. So we are fine as long as we don't see something - # like "0.'3" (gcc 4.9.0 will not allow this literal). - if re.search(r"\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$", head): - match_literal = re.match(r"^((?:\'?[0-9a-zA-Z_])*)(.*)$", "'" + tail) - collapsed += head + match_literal.group(1).replace("'", "") - elided = match_literal.group(2) - else: - second_quote = tail.find("'") - if second_quote >= 0: - collapsed += head + "''" - elided = tail[second_quote + 1 :] - else: - # Unmatched single quote - collapsed += elided - break - - return collapsed - - -def FindEndOfExpressionInLine(line, startpos, stack): - """Find the position just after the end of current parenthesized expression. - - Args: - line: a CleansedLines line. - startpos: start searching at this position. - stack: nesting stack at startpos. - - Returns: - On finding matching end: (index just after matching end, None) - On finding an unclosed expression: (-1, None) - Otherwise: (-1, new stack at end of this line) - """ - for i in range(startpos, len(line)): - char = line[i] - if char in "([{": - # Found start of parenthesized expression, push to expression stack - stack.append(char) - elif char == "<": - # Found potential start of template argument list - if i > 0 and line[i - 1] == "<": - # Left shift operator - if stack and stack[-1] == "<": - stack.pop() - if not stack: - return (-1, None) - elif i > 0 and re.search(r"\boperator\s*$", line[0:i]): - # operator<, don't add to stack - continue - else: - # Tentative start of template argument list - stack.append("<") - elif char in ")]}": - # Found end of parenthesized expression. - # - # If we are currently expecting a matching '>', the pending '<' - # must have been an operator. Remove them from expression stack. - while stack and stack[-1] == "<": - stack.pop() - if not stack: - return (-1, None) - if ( - (stack[-1] == "(" and char == ")") - or (stack[-1] == "[" and char == "]") - or (stack[-1] == "{" and char == "}") - ): - stack.pop() - if not stack: - return (i + 1, None) - else: - # Mismatched parentheses - return (-1, None) - elif char == ">": - # Found potential end of template argument list. - - # Ignore "->" and operator functions - if i > 0 and (line[i - 1] == "-" or re.search(r"\boperator\s*$", line[0 : i - 1])): - continue - - # Pop the stack if there is a matching '<'. Otherwise, ignore - # this '>' since it must be an operator. - if stack and stack[-1] == "<": - stack.pop() - if not stack: - return (i + 1, None) - elif char == ";": - # Found something that look like end of statements. If we are currently - # expecting a '>', the matching '<' must have been an operator, since - # template argument list should not contain statements. - while stack and stack[-1] == "<": - stack.pop() - if not stack: - return (-1, None) - - # Did not find end of expression or unbalanced parentheses on this line - return (-1, stack) - - -def CloseExpression(clean_lines, linenum, pos): - """If input points to ( or { or [ or <, finds the position that closes it. - - If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the - linenum/pos that correspond to the closing of the expression. - - TODO(google): cpplint spends a fair bit of time matching parentheses. - Ideally we would want to index all opening and closing parentheses once - and have CloseExpression be just a simple lookup, but due to preprocessor - tricks, this is not so easy. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: A position on the line. - - Returns: - A tuple (line, linenum, pos) pointer *past* the closing brace, or - (line, len(lines), -1) if we never find a close. Note we ignore - strings and comments when matching; and the line we return is the - 'cleansed' line at linenum. - """ - - line = clean_lines.elided[linenum] - if (line[pos] not in "({[<") or re.match(r"<[<=]", line[pos:]): - return (line, clean_lines.NumLines(), -1) - - # Check first line - (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) - if end_pos > -1: - return (line, linenum, end_pos) - - # Continue scanning forward - while stack and linenum < clean_lines.NumLines() - 1: - linenum += 1 - line = clean_lines.elided[linenum] - (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) - if end_pos > -1: - return (line, linenum, end_pos) - - # Did not find end of expression before end of file, give up - return (line, clean_lines.NumLines(), -1) - - -def FindStartOfExpressionInLine(line, endpos, stack): - """Find position at the matching start of current expression. - - This is almost the reverse of FindEndOfExpressionInLine, but note - that the input position and returned position differs by 1. - - Args: - line: a CleansedLines line. - endpos: start searching at this position. - stack: nesting stack at endpos. - - Returns: - On finding matching start: (index at matching start, None) - On finding an unclosed expression: (-1, None) - Otherwise: (-1, new stack at beginning of this line) - """ - i = endpos - while i >= 0: - char = line[i] - if char in ")]}": - # Found end of expression, push to expression stack - stack.append(char) - elif char == ">": - # Found potential end of template argument list. - # - # Ignore it if it's a "->" or ">=" or "operator>" - if i > 0 and ( - line[i - 1] == "-" - or re.match(r"\s>=\s", line[i - 1 :]) - or re.search(r"\boperator\s*$", line[0:i]) - ): - i -= 1 - else: - stack.append(">") - elif char == "<": - # Found potential start of template argument list - if i > 0 and line[i - 1] == "<": - # Left shift operator - i -= 1 - else: - # If there is a matching '>', we can pop the expression stack. - # Otherwise, ignore this '<' since it must be an operator. - if stack and stack[-1] == ">": - stack.pop() - if not stack: - return (i, None) - elif char in "([{": - # Found start of expression. - # - # If there are any unmatched '>' on the stack, they must be - # operators. Remove those. - while stack and stack[-1] == ">": - stack.pop() - if not stack: - return (-1, None) - if ( - (char == "(" and stack[-1] == ")") - or (char == "[" and stack[-1] == "]") - or (char == "{" and stack[-1] == "}") - ): - stack.pop() - if not stack: - return (i, None) - else: - # Mismatched parentheses - return (-1, None) - elif char == ";": - # Found something that look like end of statements. If we are currently - # expecting a '<', the matching '>' must have been an operator, since - # template argument list should not contain statements. - while stack and stack[-1] == ">": - stack.pop() - if not stack: - return (-1, None) - - i -= 1 - - return (-1, stack) - - -def ReverseCloseExpression(clean_lines, linenum, pos): - """If input points to ) or } or ] or >, finds the position that opens it. - - If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the - linenum/pos that correspond to the opening of the expression. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: A position on the line. - - Returns: - A tuple (line, linenum, pos) pointer *at* the opening brace, or - (line, 0, -1) if we never find the matching opening brace. Note - we ignore strings and comments when matching; and the line we - return is the 'cleansed' line at linenum. - """ - line = clean_lines.elided[linenum] - if line[pos] not in ")}]>": - return (line, 0, -1) - - # Check last line - (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) - if start_pos > -1: - return (line, linenum, start_pos) - - # Continue scanning backward - while stack and linenum > 0: - linenum -= 1 - line = clean_lines.elided[linenum] - (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) - if start_pos > -1: - return (line, linenum, start_pos) - - # Did not find start of expression before beginning of file, give up - return (line, 0, -1) - - -def CheckForCopyright(filename, lines, error): - """Logs an error if no Copyright message appears at the top of the file.""" - - # We'll say it should occur by line 10. Don't forget there's a - # placeholder line at the front. - for line in range(1, min(len(lines), 11)): - if re.search(r"Copyright", lines[line], re.IGNORECASE): - break - else: # means no copyright line was found - error( - filename, - 0, - "legal/copyright", - 5, - "No copyright message found. " - 'You should have a line: "Copyright [year] "', - ) - - -def GetIndentLevel(line): - """Return the number of leading spaces in line. - - Args: - line: A string to check. - - Returns: - An integer count of leading spaces, possibly zero. - """ - if indent := re.match(r"^( *)\S", line): - return len(indent.group(1)) - return 0 - - -def PathSplitToList(path): - """Returns the path split into a list by the separator. - - Args: - path: An absolute or relative path (e.g. '/a/b/c/' or '../a') - - Returns: - A list of path components (e.g. ['a', 'b', 'c]). - """ - lst = [] - while True: - (head, tail) = os.path.split(path) - if head == path: # absolute paths end - lst.append(head) - break - if tail == path: # relative paths end - lst.append(tail) - break - - path = head - lst.append(tail) - - lst.reverse() - return lst - - -def GetHeaderGuardCPPVariable(filename): - """Returns the CPP variable that should be used as a header guard. - - Args: - filename: The name of a C++ header file. - - Returns: - The CPP variable that should be used as a header guard in the - named file. - - """ - - # Restores original filename in case that cpplint is invoked from Emacs's - # flymake. - filename = re.sub(r"_flymake\.h$", ".h", filename) - filename = re.sub(r"/\.flymake/([^/]*)$", r"/\1", filename) - # Replace 'c++' with 'cpp'. - filename = filename.replace("C++", "cpp").replace("c++", "cpp") - - fileinfo = FileInfo(filename) - file_path_from_root = fileinfo.RepositoryName() - - def FixupPathFromRoot(): - if _root_debug: - sys.stderr.write( - f"\n_root fixup, _root = '{_root}'," - f" repository name = '{fileinfo.RepositoryName()}'\n" - ) - - # Process the file path with the --root flag if it was set. - if not _root: - if _root_debug: - sys.stderr.write("_root unspecified\n") - return file_path_from_root - - def StripListPrefix(lst, prefix): - # f(['x', 'y'], ['w, z']) -> None (not a valid prefix) - if lst[: len(prefix)] != prefix: - return None - # f(['a, 'b', 'c', 'd'], ['a', 'b']) -> ['c', 'd'] - return lst[(len(prefix)) :] - - # root behavior: - # --root=subdir , lstrips subdir from the header guard - maybe_path = StripListPrefix(PathSplitToList(file_path_from_root), PathSplitToList(_root)) - - if _root_debug: - sys.stderr.write( - ("_root lstrip (maybe_path=%s, file_path_from_root=%s," + " _root=%s)\n") - % (maybe_path, file_path_from_root, _root) - ) - - if maybe_path: - return os.path.join(*maybe_path) - - # --root=.. , will prepend the outer directory to the header guard - full_path = fileinfo.FullName() - # adapt slashes for windows - root_abspath = os.path.abspath(_root).replace("\\", "/") - - maybe_path = StripListPrefix(PathSplitToList(full_path), PathSplitToList(root_abspath)) - - if _root_debug: - sys.stderr.write( - ("_root prepend (maybe_path=%s, full_path=%s, " + "root_abspath=%s)\n") - % (maybe_path, full_path, root_abspath) - ) - - if maybe_path: - return os.path.join(*maybe_path) - - if _root_debug: - sys.stderr.write(f"_root ignore, returning {file_path_from_root}\n") - - # --root=FAKE_DIR is ignored - return file_path_from_root - - file_path_from_root = FixupPathFromRoot() - return re.sub(r"[^a-zA-Z0-9]", "_", file_path_from_root).upper() + "_" - - -def CheckForHeaderGuard(filename, clean_lines, error, cppvar): - """Checks that the file contains a header guard. - - Logs an error if no #ifndef header guard is present. For other - headers, checks that the full pathname is used. - - Args: - filename: The name of the C++ header file. - clean_lines: A CleansedLines instance containing the file. - error: The function to call with any errors found. - """ - - # Don't check for header guards if there are error suppression - # comments somewhere in this file. - # - # Because this is silencing a warning for a nonexistent line, we - # only support the very specific NOLINT(build/header_guard) syntax, - # and not the general NOLINT or NOLINT(*) syntax. - raw_lines = clean_lines.lines_without_raw_strings - for i in raw_lines: - if re.search(r"//\s*NOLINT\(build/header_guard\)", i): - return - - # Allow pragma once instead of header guards - for i in raw_lines: - if re.search(r"^\s*#pragma\s+once", i): - return - - ifndef = "" - ifndef_linenum = 0 - define = "" - endif = "" - endif_linenum = 0 - for linenum, line in enumerate(raw_lines): - linesplit = line.split() - if len(linesplit) >= 2: - # find the first occurrence of #ifndef and #define, save arg - if not ifndef and linesplit[0] == "#ifndef": - # set ifndef to the header guard presented on the #ifndef line. - ifndef = linesplit[1] - ifndef_linenum = linenum - if not define and linesplit[0] == "#define": - define = linesplit[1] - # find the last occurrence of #endif, save entire line - if line.startswith("#endif"): - endif = line - endif_linenum = linenum - - if not ifndef or not define or ifndef != define: - error( - filename, - 0, - "build/header_guard", - 5, - f"No #ifndef header guard found, suggested CPP variable is: {cppvar}", - ) - return - - # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ - # for backward compatibility. - if ifndef != cppvar: - error_level = 0 - if ifndef != cppvar + "_": - error_level = 5 - - ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, error) - error( - filename, - ifndef_linenum, - "build/header_guard", - error_level, - f"#ifndef header guard has wrong style, please use: {cppvar}", - ) - - # Check for "//" comments on endif line. - ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, error) - match = re.match(r"#endif\s*//\s*" + cppvar + r"(_)?\b", endif) - if match: - if match.group(1) == "_": - # Issue low severity warning for deprecated double trailing underscore - error( - filename, - endif_linenum, - "build/header_guard", - 0, - f'#endif line should be "#endif // {cppvar}"', - ) - return - - # Didn't find the corresponding "//" comment. If this file does not - # contain any "//" comments at all, it could be that the compiler - # only wants "/**/" comments, look for those instead. - no_single_line_comments = True - for i in range(1, len(raw_lines) - 1): - line = raw_lines[i] - if re.match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): - no_single_line_comments = False - break - - if no_single_line_comments: - match = re.match(r"#endif\s*/\*\s*" + cppvar + r"(_)?\s*\*/", endif) - if match: - if match.group(1) == "_": - # Low severity warning for double trailing underscore - error( - filename, - endif_linenum, - "build/header_guard", - 0, - f'#endif line should be "#endif /* {cppvar} */"', - ) - return - - # Didn't find anything - error( - filename, - endif_linenum, - "build/header_guard", - 5, - f'#endif line should be "#endif // {cppvar}"', - ) - - -def CheckHeaderFileIncluded(filename, include_state, error): - """Logs an error if a source file does not include its header.""" - - # Do not check test files - fileinfo = FileInfo(filename) - if re.search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): - return - - first_include = message = None - basefilename = filename[0 : len(filename) - len(fileinfo.Extension())] - for ext in GetHeaderExtensions(): - headerfile = basefilename + "." + ext - if not os.path.exists(headerfile): - continue - headername = FileInfo(headerfile).RepositoryName() - include_uses_unix_dir_aliases = False - for section_list in include_state.include_list: - for f in section_list: - include_text = f[0] - if "./" in include_text: - include_uses_unix_dir_aliases = True - if headername in include_text or include_text in headername: - return - if not first_include: - first_include = f[1] - - message = f"{fileinfo.RepositoryName()} should include its header file {headername}" - if include_uses_unix_dir_aliases: - message += ". Relative paths like . and .. are not allowed." - - if message: - error(filename, first_include, "build/include", 5, message) - - -def CheckForBadCharacters(filename, lines, error): - """Logs an error for each line containing bad characters. - - Two kinds of bad characters: - - 1. Unicode replacement characters: These indicate that either the file - contained invalid UTF-8 (likely) or Unicode replacement characters (which - it shouldn't). Note that it's possible for this to throw off line - numbering if the invalid UTF-8 occurred adjacent to a newline. - - 2. NUL bytes. These are problematic for some tools. - - Args: - filename: The name of the current file. - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - for linenum, line in enumerate(lines): - if "\ufffd" in line: - error( - filename, - linenum, - "readability/utf8", - 5, - "Line contains invalid UTF-8 (or Unicode replacement character).", - ) - if "\0" in line: - error(filename, linenum, "readability/nul", 5, "Line contains NUL byte.") - - -def CheckForNewlineAtEOF(filename, lines, error): - """Logs an error if there is no newline char at the end of the file. - - Args: - filename: The name of the current file. - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - - # The array lines() was created by adding two newlines to the - # original file (go figure), then splitting on \n. - # To verify that the file ends in \n, we just have to make sure the - # last-but-two element of lines() exists and is empty. - if len(lines) < 3 or lines[-2]: - error( - filename, - len(lines) - 2, - "whitespace/ending_newline", - 5, - "Could not find a newline character at the end of the file.", - ) - - -def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): - """Logs an error if we see /* ... */ or "..." that extend past one line. - - /* ... */ comments are legit inside macros, for one line. - Otherwise, we prefer // comments, so it's ok to warn about the - other. Likewise, it's ok for strings to extend across multiple - lines, as long as a line continuation character (backslash) - terminates each line. Although not currently prohibited by the C++ - style guide, it's ugly and unnecessary. We don't do well with either - in this lint program, so we warn about both. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Remove all \\ (escaped backslashes) from the line. They are OK, and the - # second (escaped) slash may trigger later \" detection erroneously. - line = line.replace("\\\\", "") - - if line.count("/*") > line.count("*/"): - error( - filename, - linenum, - "readability/multiline_comment", - 5, - "Complex multi-line /*...*/-style comment found. " - "Lint may give bogus warnings. " - "Consider replacing these with //-style comments, " - "with #if 0...#endif, " - "or with more clearly structured multi-line comments.", - ) - - if (line.count('"') - line.count('\\"')) % 2: - error( - filename, - linenum, - "readability/multiline_string", - 5, - 'Multi-line string ("...") found. This lint script doesn\'t ' - "do well with such strings, and may give bogus warnings. " - "Use C++11 raw strings or concatenation instead.", - ) - - -# (non-threadsafe name, thread-safe alternative, validation pattern) -# -# The validation pattern is used to eliminate false positives such as: -# _rand(); // false positive due to substring match. -# ->rand(); // some member function rand(). -# ACMRandom rand(seed); // some variable named rand. -# ISAACRandom rand(); // another variable named rand. -# -# Basically we require the return value of these functions to be used -# in some expression context on the same line by matching on some -# operator before the function name. This eliminates constructors and -# member function calls. -_UNSAFE_FUNC_PREFIX = r"(?:[-+*/=%^&|(<]\s*|>\s+)" -_THREADING_LIST = ( - ("asctime(", "asctime_r(", _UNSAFE_FUNC_PREFIX + r"asctime\([^)]+\)"), - ("ctime(", "ctime_r(", _UNSAFE_FUNC_PREFIX + r"ctime\([^)]+\)"), - ("getgrgid(", "getgrgid_r(", _UNSAFE_FUNC_PREFIX + r"getgrgid\([^)]+\)"), - ("getgrnam(", "getgrnam_r(", _UNSAFE_FUNC_PREFIX + r"getgrnam\([^)]+\)"), - ("getlogin(", "getlogin_r(", _UNSAFE_FUNC_PREFIX + r"getlogin\(\)"), - ("getpwnam(", "getpwnam_r(", _UNSAFE_FUNC_PREFIX + r"getpwnam\([^)]+\)"), - ("getpwuid(", "getpwuid_r(", _UNSAFE_FUNC_PREFIX + r"getpwuid\([^)]+\)"), - ("gmtime(", "gmtime_r(", _UNSAFE_FUNC_PREFIX + r"gmtime\([^)]+\)"), - ("localtime(", "localtime_r(", _UNSAFE_FUNC_PREFIX + r"localtime\([^)]+\)"), - ("rand(", "rand_r(", _UNSAFE_FUNC_PREFIX + r"rand\(\)"), - ("strtok(", "strtok_r(", _UNSAFE_FUNC_PREFIX + r"strtok\([^)]+\)"), - ("ttyname(", "ttyname_r(", _UNSAFE_FUNC_PREFIX + r"ttyname\([^)]+\)"), -) - - -def CheckPosixThreading(filename, clean_lines, linenum, error): - """Checks for calls to thread-unsafe functions. - - Much code has been originally written without consideration of - multi-threading. Also, engineers are relying on their old experience; - they have learned posix before threading extensions were added. These - tests guide the engineers to use thread-safe functions (when using - posix directly). - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: - # Additional pattern matching check to confirm that this is the - # function we are looking for - if re.search(pattern, line): - error( - filename, - linenum, - "runtime/threadsafe_fn", - 2, - "Consider using " - + multithread_safe_func - + "...) instead of " - + single_thread_func - + "...) for improved thread safety.", - ) - - -def CheckVlogArguments(filename, clean_lines, linenum, error): - """Checks that VLOG() is only used for defining a logging level. - - For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and - VLOG(FATAL) are not. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - if re.search(r"\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)", line): - error( - filename, - linenum, - "runtime/vlog", - 5, - "VLOG() should be used with numeric verbosity level. " - "Use LOG() if you want symbolic severity levels.", - ) - - -# Matches invalid increment: *count++, which moves pointer instead of -# incrementing a value. -_RE_PATTERN_INVALID_INCREMENT = re.compile(r"^\s*\*\w+(\+\+|--);") - - -def CheckInvalidIncrement(filename, clean_lines, linenum, error): - """Checks for invalid increment *count++. - - For example following function: - void increment_counter(int* count) { - *count++; - } - is invalid, because it effectively does count++, moving pointer, and should - be replaced with ++*count, (*count)++ or *count += 1. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - if _RE_PATTERN_INVALID_INCREMENT.match(line): - error( - filename, - linenum, - "runtime/invalid_increment", - 5, - "Changing pointer instead of value (or unused value of operator*).", - ) - - -def IsMacroDefinition(clean_lines, linenum): - if re.search(r"^#define", clean_lines[linenum]): - return True - - return bool(linenum > 0 and re.search(r"\\$", clean_lines[linenum - 1])) - - -def IsForwardClassDeclaration(clean_lines, linenum): - return re.match(r"^\s*(\btemplate\b)*.*class\s+\w+;\s*$", clean_lines[linenum]) - - -class _BlockInfo: - """Stores information about a generic block of code.""" - - def __init__(self, linenum, seen_open_brace): - self.starting_linenum = linenum - self.seen_open_brace = seen_open_brace - self.open_parentheses = 0 - self.inline_asm = _NO_ASM - self.check_namespace_indentation = False - - def CheckBegin(self, filename, clean_lines, linenum, error): - """Run checks that applies to text up to the opening brace. - - This is mostly for checking the text after the class identifier - and the "{", usually where the base class is specified. For other - blocks, there isn't much to check, so we always pass. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - pass - - def CheckEnd(self, filename, clean_lines, linenum, error): - """Run checks that applies to text after the closing brace. - - This is mostly used for checking end of namespace comments. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - pass - - def IsBlockInfo(self): - """Returns true if this block is a _BlockInfo. - - This is convenient for verifying that an object is an instance of - a _BlockInfo, but not an instance of any of the derived classes. - - Returns: - True for this class, False for derived classes. - """ - return self.__class__ == _BlockInfo - - -class _ExternCInfo(_BlockInfo): - """Stores information about an 'extern "C"' block.""" - - def __init__(self, linenum): - _BlockInfo.__init__(self, linenum, True) - - -class _ClassInfo(_BlockInfo): - """Stores information about a class.""" - - def __init__(self, name, class_or_struct, clean_lines, linenum): - _BlockInfo.__init__(self, linenum, False) - self.name = name - self.is_derived = False - self.check_namespace_indentation = True - if class_or_struct == "struct": - self.access = "public" - self.is_struct = True - else: - self.access = "private" - self.is_struct = False - - # Remember initial indentation level for this class. Using raw_lines here - # instead of elided to account for leading comments. - self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) - - # Try to find the end of the class. This will be confused by things like: - # class A { - # } *x = { ... - # - # But it's still good enough for CheckSectionSpacing. - self.last_line = 0 - depth = 0 - for i in range(linenum, clean_lines.NumLines()): - line = clean_lines.elided[i] - depth += line.count("{") - line.count("}") - if not depth: - self.last_line = i - break - - def CheckBegin(self, filename, clean_lines, linenum, error): - # Look for a bare ':' - if re.search("(^|[^:]):($|[^:])", clean_lines.elided[linenum]): - self.is_derived = True - - def CheckEnd(self, filename, clean_lines, linenum, error): - # If there is a DISALLOW macro, it should appear near the end of - # the class. - seen_last_thing_in_class = False - for i in range(linenum - 1, self.starting_linenum, -1): - match = re.search( - r"\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(" - + self.name - + r"\)", - clean_lines.elided[i], - ) - if match: - if seen_last_thing_in_class: - error( - filename, - i, - "readability/constructors", - 3, - match.group(1) + " should be the last thing in the class", - ) - break - - if not re.match(r"^\s*$", clean_lines.elided[i]): - seen_last_thing_in_class = True - - # Check that closing brace is aligned with beginning of the class. - # Only do this if the closing brace is indented by only whitespaces. - # This means we will not check single-line class definitions. - indent = re.match(r"^( *)\}", clean_lines.elided[linenum]) - if indent and len(indent.group(1)) != self.class_indent: - if self.is_struct: - parent = "struct " + self.name - else: - parent = "class " + self.name - error( - filename, - linenum, - "whitespace/indent", - 3, - f"Closing brace should be aligned with beginning of {parent}", - ) - - -class _ConstructorInfo(_BlockInfo): - """Stores information about a constructor. - For detecting member initializer lists.""" - - def __init__(self, linenum: int): - _BlockInfo.__init__(self, linenum, seen_open_brace=False) - - -class _NamespaceInfo(_BlockInfo): - """Stores information about a namespace.""" - - def __init__(self, name, linenum): - _BlockInfo.__init__(self, linenum, False) - self.name = name or "" - self.check_namespace_indentation = True - - def CheckEnd(self, filename, clean_lines, linenum, error): - """Check end of namespace comments.""" - line = clean_lines.raw_lines[linenum] - - # Check how many lines is enclosed in this namespace. Don't issue - # warning for missing namespace comments if there aren't enough - # lines. However, do apply checks if there is already an end of - # namespace comment and it's incorrect. - # - # TODO(google): We always want to check end of namespace comments - # if a namespace is large, but sometimes we also want to apply the - # check if a short namespace contained nontrivial things (something - # other than forward declarations). There is currently no logic on - # deciding what these nontrivial things are, so this check is - # triggered by namespace size only, which works most of the time. - if linenum - self.starting_linenum < 10 and not re.match( - r"^\s*};*\s*(//|/\*).*\bnamespace\b", line - ): - return - - # Look for matching comment at end of namespace. - # - # Note that we accept C style "/* */" comments for terminating - # namespaces, so that code that terminate namespaces inside - # preprocessor macros can be cpplint clean. - # - # We also accept stuff like "// end of namespace ." with the - # period at the end. - # - # Besides these, we don't accept anything else, otherwise we might - # get false negatives when existing comment is a substring of the - # expected namespace. - if self.name: - # Named namespace - if not re.match( - (r"^\s*};*\s*(//|/\*).*\bnamespace\s+" + re.escape(self.name) + r"[\*/\.\\\s]*$"), - line, - ): - error( - filename, - linenum, - "readability/namespace", - 5, - f'Namespace should be terminated with "// namespace {self.name}"', - ) - else: - # Anonymous namespace - if not re.match(r"^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$", line): - # If "// namespace anonymous" or "// anonymous namespace (more text)", - # mention "// anonymous namespace" as an acceptable form - if re.match(r"^\s*}.*\b(namespace anonymous|anonymous namespace)\b", line): - error( - filename, - linenum, - "readability/namespace", - 5, - 'Anonymous namespace should be terminated with "// namespace"' - ' or "// anonymous namespace"', - ) - else: - error( - filename, - linenum, - "readability/namespace", - 5, - 'Anonymous namespace should be terminated with "// namespace"', - ) - - -class _WrappedInfo(_BlockInfo): - """Stores information about parentheses, initializer lists, etc. - Not exactly a block but we do need the same signature. - Needed to avoid namespace indentation false positives, - though parentheses tracking would slow us down a lot - and is effectively already done by open_parentheses.""" - - pass - - -class _MemInitListInfo(_WrappedInfo): - """Stores information about member initializer lists.""" - - pass - - -class _PreprocessorInfo: - """Stores checkpoints of nesting stacks when #if/#else is seen.""" - - def __init__(self, stack_before_if): - # The entire nesting stack before #if - self.stack_before_if = stack_before_if - - # The entire nesting stack up to #else - self.stack_before_else = [] - - # Whether we have already seen #else or #elif - self.seen_else = False - - -class NestingState: - """Holds states related to parsing braces.""" - - def __init__(self): - # Stack for tracking all braces. An object is pushed whenever we - # see a "{", and popped when we see a "}". Only 3 types of - # objects are possible: - # - _ClassInfo: a class or struct. - # - _NamespaceInfo: a namespace. - # - _BlockInfo: some other type of block. - self.stack: list[_BlockInfo] = [] - - # Top of the previous stack before each Update(). - # - # Because the nesting_stack is updated at the end of each line, we - # had to do some convoluted checks to find out what is the current - # scope at the beginning of the line. This check is simplified by - # saving the previous top of nesting stack. - # - # We could save the full stack, but we only need the top. Copying - # the full nesting stack would slow down cpplint by ~10%. - self.previous_stack_top: _BlockInfo | None = None - - # The number of open parentheses in the previous stack top before the last update. - # Used to prevent false indentation detection when e.g. a function parameter is indented. - # We can't use previous_stack_top, a shallow copy whose open_parentheses value is updated. - self.previous_open_parentheses = 0 - - # The last stack item we popped. - self.popped_top: _BlockInfo | None = None - - # Stack of _PreprocessorInfo objects. - self.pp_stack = [] - - def SeenOpenBrace(self): - """Check if we have seen the opening brace for the innermost block. - - Returns: - True if we have seen the opening brace, False if the innermost - block is still expecting an opening brace. - """ - return (not self.stack) or self.stack[-1].seen_open_brace - - def InNamespaceBody(self): - """Check if we are currently one level inside a namespace body. - - Returns: - True if top of the stack is a namespace block, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _NamespaceInfo) - - def InExternC(self): - """Check if we are currently one level inside an 'extern "C"' block. - - Returns: - True if top of the stack is an extern block, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _ExternCInfo) - - def InClassDeclaration(self): - """Check if we are currently one level inside a class or struct declaration. - - Returns: - True if top of the stack is a class/struct, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _ClassInfo) - - def InAsmBlock(self): - """Check if we are currently one level inside an inline ASM block. - - Returns: - True if the top of the stack is a block containing inline ASM. - """ - return self.stack and self.stack[-1].inline_asm != _NO_ASM - - def InTemplateArgumentList(self, clean_lines, linenum, pos): - """Check if current position is inside template argument list. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: position just after the suspected template argument. - Returns: - True if (linenum, pos) is inside template arguments. - """ - while linenum < clean_lines.NumLines(): - # Find the earliest character that might indicate a template argument - line = clean_lines.elided[linenum] - match = re.match(r"^[^{};=\[\]\.<>]*(.)", line[pos:]) - if not match: - linenum += 1 - pos = 0 - continue - token = match.group(1) - pos += len(match.group(0)) - - # These things do not look like template argument list: - # class Suspect { - # class Suspect x; } - if token in ("{", "}", ";"): - return False - - # These things look like template argument list: - # template - # template - # template - # template - if token in (">", "=", "[", "]", "."): - return True - - # Check if token is an unmatched '<'. - # If not, move on to the next character. - if token != "<": - pos += 1 - if pos >= len(line): - linenum += 1 - pos = 0 - continue - - # We can't be sure if we just find a single '<', and need to - # find the matching '>'. - (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) - if end_pos < 0: - # Not sure if template argument list or syntax error in file - return False - linenum = end_line - pos = end_pos - return False - - def UpdatePreprocessor(self, line): - """Update preprocessor stack. - - We need to handle preprocessors due to classes like this: - #ifdef SWIG - struct ResultDetailsPageElementExtensionPoint { - #else - struct ResultDetailsPageElementExtensionPoint : public Extension { - #endif - - We make the following assumptions (good enough for most files): - - Preprocessor condition evaluates to true from #if up to first - #else/#elif/#endif. - - - Preprocessor condition evaluates to false from #else/#elif up - to #endif. We still perform lint checks on these lines, but - these do not affect nesting stack. - - Args: - line: current line to check. - """ - if re.match(r"^\s*#\s*(if|ifdef|ifndef)\b", line): - # Beginning of #if block, save the nesting stack here. The saved - # stack will allow us to restore the parsing state in the #else case. - self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) - elif re.match(r"^\s*#\s*(else|elif)\b", line): - # Beginning of #else block - if self.pp_stack: - if not self.pp_stack[-1].seen_else: - # This is the first #else or #elif block. Remember the - # whole nesting stack up to this point. This is what we - # keep after the #endif. - self.pp_stack[-1].seen_else = True - self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) - - # Restore the stack to how it was before the #if - self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) - else: - # TODO(google): unexpected #else, issue warning? - pass - elif re.match(r"^\s*#\s*endif\b", line): - # End of #if or #else blocks. - if self.pp_stack: - # If we saw an #else, we will need to restore the nesting - # stack to its former state before the #else, otherwise we - # will just continue from where we left off. - if self.pp_stack[-1].seen_else: - # Here we can just use a shallow copy since we are the last - # reference to it. - self.stack = self.pp_stack[-1].stack_before_else - # Drop the corresponding #if - self.pp_stack.pop() - else: - # TODO(google): unexpected #endif, issue warning? - pass - - def _Pop(self): - """Pop the innermost state (top of the stack) and remember the popped item.""" - self.popped_top = self.stack.pop() - - def _CountOpenParentheses(self, line: str): - # Count parentheses. This is to avoid adding struct arguments to - # the nesting stack. - if self.stack: - inner_block = self.stack[-1] - depth_change = line.count("(") - line.count(")") - inner_block.open_parentheses += depth_change - - # Also check if we are starting or ending an inline assembly block. - if inner_block.inline_asm in (_NO_ASM, _END_ASM): - if ( - depth_change != 0 - and inner_block.open_parentheses == 1 - and _MATCH_ASM.match(line) - ): - # Enter assembly block - inner_block.inline_asm = _INSIDE_ASM - else: - # Not entering assembly block. If previous line was _END_ASM, - # we will now shift to _NO_ASM state. - inner_block.inline_asm = _NO_ASM - elif inner_block.inline_asm == _INSIDE_ASM and inner_block.open_parentheses == 0: - # Exit assembly block - inner_block.inline_asm = _END_ASM - - def _UpdateNamesapce(self, line: str, linenum: int) -> str | None: - """ - Match start of namespace, append to stack, and consume line - Args: - line: Line to check and consume - linenum: Line number of the line to check - - Returns: - The consumed line if namespace matched; None otherwise - """ - # Match start of namespace. The "\b\s*" below catches namespace - # declarations even if it weren't followed by a whitespace, this - # is so that we don't confuse our namespace checker. The - # missing spaces will be flagged by CheckSpacing. - namespace_decl_match = re.match(r"^\s*namespace\b\s*([:\w]+)?(.*)$", line) - if not namespace_decl_match: - return None - - new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) - self.stack.append(new_namespace) - - line = namespace_decl_match.group(2) - if line.find("{") != -1: - new_namespace.seen_open_brace = True - line = line[line.find("{") + 1 :] - return line - - def _UpdateConstructor(self, line: str, linenum: int, class_name: str | None = None): - """ - Check if the given line is a constructor. - Args: - line: Line to check. - class_name: If line checked is inside of a class block, a str of the class's name; - otherwise, None. - """ - if not class_name: - if not re.match(r"\s*(\w*)\s*::\s*\1\s*\(", line): - return - elif not re.match(rf"\s*{re.escape(class_name)}\s*\(", line): - return - - self.stack.append(_ConstructorInfo(linenum)) - - # TODO(google): Update() is too long, but we will refactor later. - def Update(self, filename: str, clean_lines: CleansedLines, linenum: int, error): - """Update nesting state with current line. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Remember top of the previous nesting stack. - # - # The stack is always pushed/popped and not modified in place, so - # we can just do a shallow copy instead of copy.deepcopy. Using - # deepcopy would slow down cpplint by ~28%. - if self.stack: - self.previous_stack_top = self.stack[-1] - self.previous_open_parentheses = self.stack[-1].open_parentheses - else: - self.previous_stack_top = None - - # Update pp_stack - self.UpdatePreprocessor(line) - - self._CountOpenParentheses(line) - - # Consume namespace declaration at the beginning of the line. Do - # this in a loop so that we catch same line declarations like this: - # namespace proto2 { namespace bridge { class MessageSet; } } - while (new_line := self._UpdateNamesapce(line, linenum)) is not None: # could be empty str - line = new_line - - # Look for a class declaration in whatever is left of the line - # after parsing namespaces. The regexp accounts for decorated classes - # such as in: - # class LOCKABLE API Object { - # }; - class_decl_match = re.match( - r"^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?" - r"(class|struct)\s+(?:[a-zA-Z0-9_]+\s+)*(\w+(?:::\w+)*))" - r"(.*)$", - line, - ) - if class_decl_match and (not self.stack or self.stack[-1].open_parentheses == 0): - # We do not want to accept classes that are actually template arguments: - # template , - # template class Ignore3> - # void Function() {}; - # - # To avoid template argument cases, we scan forward and look for - # an unmatched '>'. If we see one, assume we are inside a - # template argument list. - end_declaration = len(class_decl_match.group(1)) - if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): - self.stack.append( - _ClassInfo( - class_decl_match.group(3), class_decl_match.group(2), clean_lines, linenum - ) - ) - line = class_decl_match.group(4) - - # If we have not yet seen the opening brace for the innermost block, - # run checks here. - if not self.SeenOpenBrace(): - self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) - - # Update access control if we are directly inside a class/struct - if self.stack and isinstance(self.stack[-1], _ClassInfo): - if self.stack[-1].seen_open_brace: - classinfo: _ClassInfo = self.stack[-1] - # Update access control - if access_match := re.match( - r"^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?" - r":([^:].*|$)", - line, - ): - classinfo.access = access_match.group(2) - - # Check that access keywords are indented +1 space. Skip this - # check if the keywords are not preceded by whitespaces. - indent = access_match.group(1) - if len(indent) != classinfo.class_indent + 1 and re.match(r"^\s*$", indent): - if classinfo.is_struct: - parent = "struct " + classinfo.name - else: - parent = "class " + classinfo.name - slots = "" - if access_match.group(3): - slots = access_match.group(3) - error( - filename, - linenum, - "whitespace/indent", - 3, - f"{access_match.group(2)}{slots}:" - f" should be indented +1 space inside {parent}", - ) - - line = access_match.group(4) - else: - self._UpdateConstructor(line, linenum, class_name=classinfo.name) - else: # Not in class - self._UpdateConstructor(line, linenum) - - # If brace not open and we just finished a parenthetical definition, - # check if we're in a member initializer list following a constructor. - if ( - self.stack - and ( - isinstance(self.stack[-1], _ConstructorInfo) - or isinstance(self.previous_stack_top, _ConstructorInfo) - ) - and not self.stack[-1].seen_open_brace - and re.search(r"[^:]:[^:]", line) - ): - self.stack.append(_MemInitListInfo(linenum, seen_open_brace=False)) - - # Consume braces or semicolons from what's left of the line - while True: - # Match first brace, semicolon, or closed parenthesis. - matched = re.match(r"^[^{;)}]*([{;)}])(.*)$", line) - if not matched: - break - - token = matched.group(1) - if token == "{": - # If namespace or class hasn't seen an opening brace yet, mark - # namespace/class head as complete. Push a new block onto the - # stack otherwise. - if not self.SeenOpenBrace(): - # End of initializer list wrap if present - if isinstance(self.stack[-1], _MemInitListInfo): - self._Pop() - self.stack[-1].seen_open_brace = True - elif re.match(r'^extern\s*"[^"]*"\s*\{', line): - self.stack.append(_ExternCInfo(linenum)) - else: - self.stack.append(_BlockInfo(linenum, True)) - if _MATCH_ASM.match(line): - self.stack[-1].inline_asm = _BLOCK_ASM - elif token == ";": - # If we haven't seen an opening brace yet, but we already saw - # a semicolon, this is probably a forward declaration. Pop - # the stack for these. - if not self.SeenOpenBrace(): - self._Pop() - elif token == ")": - # Similarly, if we haven't seen an opening brace yet, but we - # already saw a closing parenthesis, then these are probably - # function arguments with extra "class" or "struct" keywords. - # Also pop these stack for these. - if ( - self.stack - and not self.stack[-1].seen_open_brace - and isinstance(self.stack[-1], _ClassInfo) - ): - self._Pop() - else: # token == '}' - # Perform end of block checks and pop the stack. - if self.stack: - self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) - self._Pop() - line = matched.group(2) - - def InnermostClass(self): - """Get class info on the top of the stack. - - Returns: - A _ClassInfo object if we are inside a class, or None otherwise. - """ - for i in range(len(self.stack), 0, -1): - classinfo = self.stack[i - 1] - if isinstance(classinfo, _ClassInfo): - return classinfo - return None - - -def CheckForNonStandardConstructs(filename, clean_lines, linenum, nesting_state, error): - r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. - - Complain about several constructs which gcc-2 accepts, but which are - not standard C++. Warning about these in lint is one way to ease the - transition to new compilers. - - put storage class first (e.g. "static const" instead of "const static"). - - "%lld" instead of %qd" in printf-type functions. - - "%1$d" is non-standard in printf-type functions. - - "\%" is an undefined character escape sequence. - - text after #endif is not allowed. - - invalid inner-style forward declaration. - - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?", line): - error( - filename, - linenum, - "build/deprecated", - 3, - ">? and ))?' - # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' - error( - filename, - linenum, - "runtime/member_string_references", - 2, - "const string& members are dangerous. It is much better to use " - "alternatives, such as pointers or simple constants.", - ) - - # Everything else in this function operates on class declarations. - # Return early if the top of the nesting stack is not a class, or if - # the class head is not completed yet. - classinfo = nesting_state.InnermostClass() - if not classinfo or not classinfo.seen_open_brace: - return - - # The class may have been declared with namespace or classname qualifiers. - # The constructor and destructor will not have those qualifiers. - base_classname = classinfo.name.split("::")[-1] - - # Look for single-argument constructors that aren't marked explicit. - # Technically a valid construct, but against style. - explicit_constructor_match = re.match( - r"\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?" - rf"(?:(?:inline|constexpr)\s+)*{re.escape(base_classname)}\s*" - r"\(((?:[^()]|\([^()]*\))*)\)", - line, - ) - - if explicit_constructor_match: - is_marked_explicit = explicit_constructor_match.group(1) - - if not explicit_constructor_match.group(2): - constructor_args = [] - else: - constructor_args = explicit_constructor_match.group(2).split(",") - - # collapse arguments so that commas in template parameter lists and function - # argument parameter lists don't split arguments in two - i = 0 - while i < len(constructor_args): - constructor_arg = constructor_args[i] - while constructor_arg.count("<") > constructor_arg.count(">") or constructor_arg.count( - "(" - ) > constructor_arg.count(")"): - constructor_arg += "," + constructor_args[i + 1] - del constructor_args[i + 1] - constructor_args[i] = constructor_arg - i += 1 - - variadic_args = [arg for arg in constructor_args if "&&..." in arg] - defaulted_args = [arg for arg in constructor_args if "=" in arg] - noarg_constructor = ( - not constructor_args # empty arg list - or - # 'void' arg specifier - (len(constructor_args) == 1 and constructor_args[0].strip() == "void") - ) - onearg_constructor = ( - ( - len(constructor_args) == 1 # exactly one arg - and not noarg_constructor - ) - or - # all but at most one arg defaulted - ( - len(constructor_args) >= 1 - and not noarg_constructor - and len(defaulted_args) >= len(constructor_args) - 1 - ) - or - # variadic arguments with zero or one argument - (len(constructor_args) <= 2 and len(variadic_args) >= 1) - ) - initializer_list_constructor = bool( - onearg_constructor - and re.search(r"\bstd\s*::\s*initializer_list\b", constructor_args[0]) - ) - copy_constructor = bool( - onearg_constructor - and re.match( - r"((const\s+(volatile\s+)?)?|(volatile\s+(const\s+)?))?" - rf"{re.escape(base_classname)}(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&", - constructor_args[0].strip(), - ) - ) - - if ( - not is_marked_explicit - and onearg_constructor - and not initializer_list_constructor - and not copy_constructor - ): - if defaulted_args or variadic_args: - error( - filename, - linenum, - "runtime/explicit", - 4, - "Constructors callable with one argument should be marked explicit.", - ) - else: - error( - filename, - linenum, - "runtime/explicit", - 4, - "Single-parameter constructors should be marked explicit.", - ) - - -def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): - """Checks for the correctness of various spacing around function calls. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Since function calls often occur inside if/for/while/switch - # expressions - which have their own, more liberal conventions - we - # first see if we should be looking inside such an expression for a - # function call, to which we can apply more strict standards. - fncall = line # if there's no control flow construct, look at whole line - for pattern in ( - r"\bif\s*\((.*)\)\s*{", - r"\bfor\s*\((.*)\)\s*{", - r"\bwhile\s*\((.*)\)\s*[{;]", - r"\bswitch\s*\((.*)\)\s*{", - ): - match = re.search(pattern, line) - if match: - fncall = match.group(1) # look inside the parens for function calls - break - - # Except in if/for/while/switch, there should never be space - # immediately inside parens (eg "f( 3, 4 )"). We make an exception - # for nested parens ( (a+b) + c ). Likewise, there should never be - # a space before a ( when it's a function argument. I assume it's a - # function argument when the char before the whitespace is legal in - # a function name (alnum + _) and we're not starting a macro. Also ignore - # pointers and references to arrays and functions coz they're too tricky: - # we use a very simple way to recognize these: - # " (something)(maybe-something)" or - # " (something)(maybe-something," or - # " (something)[something]" - # Note that we assume the contents of [] to be short enough that - # they'll never need to wrap. - if ( # Ignore control structures. - not re.search(r"\b(if|elif|for|while|switch|return|new|delete|catch|sizeof)\b", fncall) - and - # Ignore pointers/references to functions. - not re.search(r" \([^)]+\)\([^)]*(\)|,$)", fncall) - and - # Ignore pointers/references to arrays. - not re.search(r" \([^)]+\)\[[^\]]+\]", fncall) - ): - if re.search(r"\w\s*\(\s(?!\s*\\$)", fncall): # a ( used for a fn call - error(filename, linenum, "whitespace/parens", 4, "Extra space after ( in function call") - elif re.search(r"\(\s+(?!(\s*\\)|\()", fncall): - error(filename, linenum, "whitespace/parens", 2, "Extra space after (") - if ( - re.search(r"\w\s+\(", fncall) - and not re.search(r"_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(", fncall) - and not re.search(r"#\s*define|typedef|using\s+\w+\s*=", fncall) - and not re.search(r"\w\s+\((\w+::)*\*\w+\)\(", fncall) - and not re.search(r"\bcase\s+\(", fncall) - ): - # TODO(google): Space after an operator function seem to be a common - # error, silence those for now by restricting them to highest verbosity. - if re.search(r"\boperator_*\b", line): - error( - filename, - linenum, - "whitespace/parens", - 0, - "Extra space before ( in function call", - ) - else: - error( - filename, - linenum, - "whitespace/parens", - 4, - "Extra space before ( in function call", - ) - # If the ) is followed only by a newline or a { + newline, assume it's - # part of a control statement (if/while/etc), and don't complain - if re.search(r"[^)]\s+\)\s*[^{\s]", fncall): - # If the closing parenthesis is preceded by only whitespaces, - # try to give a more descriptive error message. - if re.search(r"^\s+\)", fncall): - error( - filename, - linenum, - "whitespace/parens", - 2, - "Closing ) should be moved to the previous line", - ) - else: - error(filename, linenum, "whitespace/parens", 2, "Extra space before )") - - -def IsBlankLine(line): - """Returns true if the given line is blank. - - We consider a line to be blank if the line is empty or consists of - only white spaces. - - Args: - line: A line of a string. - - Returns: - True, if the given line is blank. - """ - return not line or line.isspace() - - -def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, error): - is_namespace_indent_item = len(nesting_state.stack) >= 1 and ( - isinstance(nesting_state.stack[-1], _NamespaceInfo) - or (isinstance(nesting_state.previous_stack_top, _NamespaceInfo)) - ) - - if ShouldCheckNamespaceIndentation( - nesting_state, is_namespace_indent_item, clean_lines.elided, line - ): - CheckItemIndentationInNamespace(filename, clean_lines.elided, line, error) - - -def CheckForFunctionLengths(filename, clean_lines, linenum, function_state, error): - """Reports for long function bodies. - - For an overview why this is done, see: - https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions - - Uses a simplistic algorithm assuming other style guidelines - (especially spacing) are followed. - Only checks unindented functions, so class members are unchecked. - Trivial bodies are unchecked, so constructors with huge initializer lists - may be missed. - Blank/comment lines are not counted so as to avoid encouraging the removal - of vertical space and comments just to get through a lint check. - NOLINT *on the last line of a function* disables this check. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - function_state: Current function name and lines in body so far. - error: The function to call with any errors found. - """ - lines = clean_lines.lines - line = lines[linenum] - joined_line = "" - - starting_func = False - regexp = r"(\w(\w|::|\*|\&|\s)*)\(" # decls * & space::name( ... - if match_result := re.match(regexp, line): - # If the name is all caps and underscores, figure it's a macro and - # ignore it, unless it's TEST or TEST_F. - function_name = match_result.group(1).split()[-1] - if function_name in {"TEST", "TEST_F"} or not re.match(r"[A-Z_]+$", function_name): - starting_func = True - - if starting_func: - body_found = False - for start_linenum in range(linenum, clean_lines.NumLines()): - start_line = lines[start_linenum] - joined_line += " " + start_line.lstrip() - if re.search(r"(;|})", start_line): # Declarations and trivial functions - body_found = True - break # ... ignore - if re.search(r"{", start_line): - body_found = True - function = re.search(r"((\w|:)*)\(", line).group(1) - if re.match(r"TEST", function): # Handle TEST... macros - parameter_regexp = re.search(r"(\(.*\))", joined_line) - if parameter_regexp: # Ignore bad syntax - function += parameter_regexp.group(1) - else: - function += "()" - function_state.Begin(function) - break - if not body_found: - # No body for the function (or evidence of a non-function) was found. - error( - filename, - linenum, - "readability/fn_size", - 5, - "Lint failed to find start of function body.", - ) - elif re.match(r"^\}\s*$", line): # function end - function_state.Check(error, filename, linenum) - function_state.End() - elif not re.match(r"^\s*$", line): - function_state.Count() # Count non-blank/non-comment lines. - - -_RE_PATTERN_TODO = re.compile(r"^//(\s*)TODO(\(.+?\))?:?(\s|$)?") - - -def CheckComment(line, filename, linenum, next_line_start, error): - """Checks for common mistakes in comments. - - Args: - line: The line in question. - filename: The name of the current file. - linenum: The number of the line to check. - next_line_start: The first non-whitespace column of the next line. - error: The function to call with any errors found. - """ - commentpos = line.find("//") - if commentpos != -1: - # Check if the // may be in quotes. If so, ignore it - if re.sub(r"\\.", "", line[0:commentpos]).count('"') % 2 == 0: - # Allow one space for new scopes, two spaces otherwise: - if not (re.match(r"^.*{ *//", line) and next_line_start == commentpos) and ( - (commentpos >= 1 and line[commentpos - 1] not in string.whitespace) - or (commentpos >= 2 and line[commentpos - 2] not in string.whitespace) - ): - error( - filename, - linenum, - "whitespace/comments", - 2, - "At least two spaces is best between code and comments", - ) - - # Checks for common mistakes in TODO comments. - comment = line[commentpos:] - match = _RE_PATTERN_TODO.match(comment) - if match: - # One whitespace is correct; zero whitespace is handled elsewhere. - leading_whitespace = match.group(1) - if len(leading_whitespace) > 1: - error(filename, linenum, "whitespace/todo", 2, "Too many spaces before TODO") - - username = match.group(2) - if not username: - error( - filename, - linenum, - "readability/todo", - 2, - "Missing username in TODO; it should look like " - '"// TODO(my_username): Stuff."', - ) - - middle_whitespace = match.group(3) - # Comparisons made explicit for correctness - # -- pylint: disable=g-explicit-bool-comparison - if middle_whitespace not in {" ", ""}: - error( - filename, - linenum, - "whitespace/todo", - 2, - "TODO(my_username) should be followed by a space", - ) - - # If the comment contains an alphanumeric character, there - # should be a space somewhere between it and the // unless - # it's a /// or //! Doxygen comment. - if re.match(r"//[^ ]*\w", comment) and not re.match(r"(///|//\!)(\s+|$)", comment): - error( - filename, - linenum, - "whitespace/comments", - 4, - "Should have a space between // and comment", - ) - - -def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): - """Checks for the correctness of various spacing issues in the code. - - Things we check for: spaces around operators, spaces after - if/for/while/switch, no spaces around parens in function calls, two - spaces between code and comment, don't start a block with a blank - line, don't end a function with a blank line, don't add a blank line - after public/protected/private, don't have too many blank lines in a row. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - - # Don't use "elided" lines here, otherwise we can't check commented lines. - # Don't want to use "raw" either, because we don't want to check inside C++11 - # raw strings, - raw = clean_lines.lines_without_raw_strings - line = raw[linenum] - - # Before nixing comments, check if the line is blank for no good - # reason. This includes the first line after a block is opened, and - # blank lines at the end of a function (ie, right before a line like '}' - # - # Skip all the blank line checks if we are immediately inside a - # namespace body. In other words, don't issue blank line warnings - # for this block: - # namespace { - # - # } - # - # A warning about missing end of namespace comments will be issued instead. - # - # Also skip blank line checks for 'extern "C"' blocks, which are formatted - # like namespaces. - if IsBlankLine(line) and not nesting_state.InNamespaceBody() and not nesting_state.InExternC(): - elided = clean_lines.elided - prev_line = elided[linenum - 1] - prevbrace = prev_line.rfind("{") - # TODO(google): Don't complain if line before blank line, and line after, - # both start with alnums and are indented the same amount. - # This ignores whitespace at the start of a namespace block - # because those are not usually indented. - if prevbrace != -1 and prev_line[prevbrace:].find("}") == -1: - # OK, we have a blank line at the start of a code block. Before we - # complain, we check if it is an exception to the rule: The previous - # non-empty line has the parameters of a function header that are indented - # 4 spaces (because they did not fit in a 80 column line when placed on - # the same line as the function name). We also check for the case where - # the previous line is indented 6 spaces, which may happen when the - # initializers of a constructor do not fit into a 80 column line. - exception = False - if re.match(r" {6}\w", prev_line): # Initializer list? - # We are looking for the opening column of initializer list, which - # should be indented 4 spaces to cause 6 space indentation afterwards. - search_position = linenum - 2 - while search_position >= 0 and re.match(r" {6}\w", elided[search_position]): - search_position -= 1 - exception = search_position >= 0 and elided[search_position][:5] == " :" - else: - # Search for the function arguments or an initializer list. We use a - # simple heuristic here: If the line is indented 4 spaces; and we have a - # closing paren, without the opening paren, followed by an opening brace - # or colon (for initializer lists) we assume that it is the last line of - # a function header. If we have a colon indented 4 spaces, it is an - # initializer list. - exception = re.match( - r" {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)", prev_line - ) or re.match(r" {4}:", prev_line) - - if not exception: - error( - filename, - linenum, - "whitespace/blank_line", - 2, - "Redundant blank line at the start of a code block should be deleted.", - ) - # Ignore blank lines at the end of a block in a long if-else - # chain, like this: - # if (condition1) { - # // Something followed by a blank line - # - # } else if (condition2) { - # // Something else - # } - if linenum + 1 < clean_lines.NumLines(): - next_line = raw[linenum + 1] - if next_line and re.match(r"\s*}", next_line) and next_line.find("} else ") == -1: - error( - filename, - linenum, - "whitespace/blank_line", - 3, - "Redundant blank line at the end of a code block should be deleted.", - ) - - matched = re.match(r"\s*(public|protected|private):", prev_line) - if matched: - error( - filename, - linenum, - "whitespace/blank_line", - 3, - f'Do not leave a blank line after "{matched.group(1)}:"', - ) - - # Next, check comments - next_line_start = 0 - if linenum + 1 < clean_lines.NumLines(): - next_line = raw[linenum + 1] - next_line_start = len(next_line) - len(next_line.lstrip()) - CheckComment(line, filename, linenum, next_line_start, error) - - # get rid of comments and strings - line = clean_lines.elided[linenum] - - # You shouldn't have spaces before your brackets, except for C++11 attributes - # or maybe after 'delete []', 'return []() {};', or 'auto [abc, ...] = ...;'. - if re.search(r"\w\s+\[(?!\[)", line) and not re.search(r"(?:auto&?|delete|return)\s+\[", line): - error(filename, linenum, "whitespace/braces", 5, "Extra space before [") - - # In range-based for, we wanted spaces before and after the colon, but - # not around "::" tokens that might appear. - if re.search(r"for *\(.*[^:]:[^: ]", line) or re.search(r"for *\(.*[^: ]:[^:]", line): - error( - filename, - linenum, - "whitespace/forcolon", - 2, - "Missing space around colon in range-based for loop", - ) - - -def CheckOperatorSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing around operators. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Don't try to do spacing checks for operator methods. Do this by - # replacing the troublesome characters with something else, - # preserving column position for all other characters. - # - # The replacement is done repeatedly to avoid false positives from - # operators that call operators. - while True: - match = re.match(r"^(.*\boperator\b)(\S+)(\s*\(.*)$", line) - if match: - line = match.group(1) + ("_" * len(match.group(2))) + match.group(3) - else: - break - - # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". - # Otherwise not. Note we only check for non-spaces on *both* sides; - # sometimes people put non-spaces on one side when aligning ='s among - # many lines (not that this is behavior that I approve of...) - if ( - (re.search(r"[\w.]=", line) or re.search(r"=[\w.]", line)) - and not re.search(r"\b(if|while|for) ", line) - # Operators taken from [lex.operators] in C++11 standard. - and not re.search(r"(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)", line) - and not re.search(r"operator=", line) - ): - error(filename, linenum, "whitespace/operators", 4, "Missing spaces around =") - - # It's ok not to have spaces around binary operators like + - * /, but if - # there's too little whitespace, we get concerned. It's hard to tell, - # though, so we punt on this one for now. TODO(google). - - # You should always have whitespace around binary operators. - # - # Check <= and >= first to avoid false positives with < and >, then - # check non-include lines for spacing around < and >. - # - # If the operator is followed by a comma, assume it's be used in a - # macro context and don't do any checks. This avoids false - # positives. - # - # Note that && is not included here. This is because there are too - # many false positives due to RValue references. - match = re.search(r"[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]", line) - if match: - # TODO(google): support alternate operators - error( - filename, linenum, "whitespace/operators", 3, f"Missing spaces around {match.group(1)}" - ) - elif not re.match(r"#.*include", line): - # Look for < that is not surrounded by spaces. This is only - # triggered if both sides are missing spaces, even though - # technically should should flag if at least one side is missing a - # space. This is done to avoid some false positives with shifts. - match = re.match(r"^(.*[^\s<])<[^\s=<,]", line) - if match: - (_, _, end_pos) = CloseExpression(clean_lines, linenum, len(match.group(1))) - if end_pos <= -1: - error(filename, linenum, "whitespace/operators", 3, "Missing spaces around <") - - # Look for > that is not surrounded by spaces. Similar to the - # above, we only trigger if both sides are missing spaces to avoid - # false positives with shifts. - match = re.match(r"^(.*[^-\s>])>[^\s=>,]", line) - if match: - (_, _, start_pos) = ReverseCloseExpression(clean_lines, linenum, len(match.group(1))) - if start_pos <= -1: - error(filename, linenum, "whitespace/operators", 3, "Missing spaces around >") - - # We allow no-spaces around << when used like this: 10<<20, but - # not otherwise (particularly, not when used as streams) - # - # We also allow operators following an opening parenthesis, since - # those tend to be macros that deal with operators. - match = re.search(r"(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])", line) - if ( - match - and not (match.group(1).isdigit() and match.group(2).isdigit()) - and not (match.group(1) == "operator" and match.group(2) == ";") - ): - error(filename, linenum, "whitespace/operators", 3, "Missing spaces around <<") - - # We allow no-spaces around >> for almost anything. This is because - # C++11 allows ">>" to close nested templates, which accounts for - # most cases when ">>" is not followed by a space. - # - # We still warn on ">>" followed by alpha character, because that is - # likely due to ">>" being used for right shifts, e.g.: - # value >> alpha - # - # When ">>" is used to close templates, the alphanumeric letter that - # follows would be part of an identifier, and there should still be - # a space separating the template type and the identifier. - # type> alpha - match = re.search(r">>[a-zA-Z_]", line) - if match: - error(filename, linenum, "whitespace/operators", 3, "Missing spaces around >>") - - # There shouldn't be space around unary operators - match = re.search(r"(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])", line) - if match: - error( - filename, - linenum, - "whitespace/operators", - 4, - f"Extra space for operator {match.group(1)}", - ) - - -def CheckParenthesisSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing around parentheses. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # No spaces after an if, while, switch, or for - match = re.search(r" (if\(|for\(|while\(|switch\()", line) - if match: - error( - filename, linenum, "whitespace/parens", 5, f"Missing space before ( in {match.group(1)}" - ) - - # For if/for/while/switch, the left and right parens should be - # consistent about how many spaces are inside the parens, and - # there should either be zero or one spaces inside the parens. - # We don't want: "if ( foo)" or "if ( foo )". - # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. - match = re.search( - r"\b(if|for|while|switch)\s*" - r"\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$", - line, - ) - if match: - if len(match.group(2)) != len(match.group(4)) and not ( - match.group(3) == ";" - and len(match.group(2)) == 1 + len(match.group(4)) - or not match.group(2) - and re.search(r"\bfor\s*\(.*; \)", line) - ): - error( - filename, - linenum, - "whitespace/parens", - 5, - f"Mismatching spaces inside () in {match.group(1)}", - ) - if len(match.group(2)) not in [0, 1]: - error( - filename, - linenum, - "whitespace/parens", - 5, - f"Should have zero or one spaces inside ( and ) in {match.group(1)}", - ) - - -def CheckCommaSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing near commas and semicolons. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - raw = clean_lines.lines_without_raw_strings - line = clean_lines.elided[linenum] - - # You should always have a space after a comma (either as fn arg or operator) - # - # This does not apply when the non-space character following the - # comma is another comma, since the only time when that happens is - # for empty macro arguments. - # - # We run this check in two passes: first pass on elided lines to - # verify that lines contain missing whitespaces, second pass on raw - # lines to confirm that those missing whitespaces are not due to - # elided comments. - match = re.search( - r",[^,\s]", re.sub(r"\b__VA_OPT__\s*\(,\)", "", re.sub(r"\boperator\s*,\s*\(", "F(", line)) - ) - if match and re.search(r",[^,\s]", raw[linenum]): - error(filename, linenum, "whitespace/comma", 3, "Missing space after ,") - - # You should always have a space after a semicolon - # except for few corner cases - # TODO(google): clarify if 'if (1) { return 1;}' is requires one more - # space after ; - if re.search(r";[^\s};\\)/]", line): - error(filename, linenum, "whitespace/semicolon", 3, "Missing space after ;") - - -def _IsType(clean_lines, nesting_state, expr): - """Check if expression looks like a type name, returns true if so. - - Args: - clean_lines: A CleansedLines instance containing the file. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - expr: The expression to check. - Returns: - True, if token looks like a type. - """ - # Keep only the last token in the expression - if last_word := re.match(r"^.*(\b\S+)$", expr): - token = last_word.group(1) - else: - token = expr - - # Match native types and stdint types - if _TYPES.match(token): - return True - - # Try a bit harder to match templated types. Walk up the nesting - # stack until we find something that resembles a typename - # declaration for what we are looking for. - typename_pattern = r"\b(?:typename|class|struct)\s+" + re.escape(token) + r"\b" - block_index = len(nesting_state.stack) - 1 - while block_index >= 0: - if isinstance(nesting_state.stack[block_index], _NamespaceInfo): - return False - - # Found where the opening brace is. We want to scan from this - # line up to the beginning of the function, minus a few lines. - # template - # class C - # : public ... { // start scanning here - last_line = nesting_state.stack[block_index].starting_linenum - - next_block_start = 0 - if block_index > 0: - next_block_start = nesting_state.stack[block_index - 1].starting_linenum - first_line = last_line - while first_line >= next_block_start: - if clean_lines.elided[first_line].find("template") >= 0: - break - first_line -= 1 - if first_line < next_block_start: - # Didn't find any "template" keyword before reaching the next block, - # there are probably no template things to check for this block - block_index -= 1 - continue - - # Look for typename in the specified range - for i in range(first_line, last_line + 1, 1): - if re.search(typename_pattern, clean_lines.elided[i]): - return True - block_index -= 1 - - return False - - -def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): - """Checks for horizontal spacing near commas. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Except after an opening paren, or after another opening brace (in case of - # an initializer list, for instance), you should have spaces before your - # braces when they are delimiting blocks, classes, namespaces etc. - # And since you should never have braces at the beginning of a line, - # this is an easy test. Except that braces used for initialization don't - # follow the same rule; we often don't want spaces before those. - - if match := re.match(r"^(.*[^ ({>]){", line): - # Try a bit harder to check for brace initialization. This - # happens in one of the following forms: - # Constructor() : initializer_list_{} { ... } - # Constructor{}.MemberFunction() - # Type variable{}; - # FunctionCall(type{}, ...); - # LastArgument(..., type{}); - # LOG(INFO) << type{} << " ..."; - # map_of_type[{...}] = ...; - # ternary = expr ? new type{} : nullptr; - # OuterTemplate{}> - # - # We check for the character following the closing brace, and - # silence the warning if it's one of those listed above, i.e. - # "{.;,)<>]:". - # - # To account for nested initializer list, we allow any number of - # closing braces up to "{;,)<". We can't simply silence the - # warning on first sight of closing brace, because that would - # cause false negatives for things that are not initializer lists. - # Silence this: But not this: - # Outer{ if (...) { - # Inner{...} if (...){ // Missing space before { - # }; } - # - # There is a false negative with this approach if people inserted - # spurious semicolons, e.g. "if (cond){};", but we will catch the - # spurious semicolon with a separate check. - leading_text = match.group(1) - (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, len(match.group(1))) - trailing_text = "" - if endpos > -1: - trailing_text = endline[endpos:] - for offset in range(endlinenum + 1, min(endlinenum + 3, clean_lines.NumLines() - 1)): - trailing_text += clean_lines.elided[offset] - # We also suppress warnings for `uint64_t{expression}` etc., as the style - # guide recommends brace initialization for integral types to avoid - # overflow/truncation. - if not re.match(r"^[\s}]*[{.;,)<>\]:]", trailing_text) and not _IsType( - clean_lines, nesting_state, leading_text - ): - error(filename, linenum, "whitespace/braces", 5, "Missing space before {") - - # Make sure '} else {' has spaces. - if re.search(r"}else", line): - error(filename, linenum, "whitespace/braces", 5, "Missing space before else") - - # You shouldn't have a space before a semicolon at the end of the line. - # There's a special case for "for" since the style guide allows space before - # the semicolon there. - if re.search(r":\s*;\s*$", line): - error( - filename, - linenum, - "whitespace/semicolon", - 5, - "Semicolon defining empty statement. Use {} instead.", - ) - elif re.search(r"^\s*;\s*$", line): - error( - filename, - linenum, - "whitespace/semicolon", - 5, - "Line contains only semicolon. If this should be an empty statement, use {} instead.", - ) - elif re.search(r"\s+;\s*$", line) and not re.search(r"\bfor\b", line): - error( - filename, - linenum, - "whitespace/semicolon", - 5, - "Extra space before last semicolon. If this should be an empty " - "statement, use {} instead.", - ) - - -def IsDecltype(clean_lines, linenum, column): - """Check if the token ending on (linenum, column) is decltype(). - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: the number of the line to check. - column: end column of the token to check. - Returns: - True if this token is decltype() expression, False otherwise. - """ - (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) - if start_col < 0: - return False - return bool(re.search(r"\bdecltype\s*$", text[0:start_col])) - - -def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): - """Checks for additional blank line issues related to sections. - - Currently the only thing checked here is blank line before protected/private. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - class_info: A _ClassInfo objects. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Skip checks if the class is small, where small means 25 lines or less. - # 25 lines seems like a good cutoff since that's the usual height of - # terminals, and any class that can't fit in one screen can't really - # be considered "small". - # - # Also skip checks if we are on the first line. This accounts for - # classes that look like - # class Foo { public: ... }; - # - # If we didn't find the end of the class, last_line would be zero, - # and the check will be skipped by the first condition. - if ( - class_info.last_line - class_info.starting_linenum <= 24 - or linenum <= class_info.starting_linenum - ): - return - - matched = re.match(r"\s*(public|protected|private):", clean_lines.lines[linenum]) - if matched: - # Issue warning if the line before public/protected/private was - # not a blank line, but don't do this if the previous line contains - # "class" or "struct". This can happen two ways: - # - We are at the beginning of the class. - # - We are forward-declaring an inner class that is semantically - # private, but needed to be public for implementation reasons. - # Also ignores cases where the previous line ends with a backslash as can be - # common when defining classes in C macros. - prev_line = clean_lines.lines[linenum - 1] - if ( - not IsBlankLine(prev_line) - and not re.search(r"\b(class|struct)\b", prev_line) - and not re.search(r"\\$", prev_line) - ): - # Try a bit harder to find the beginning of the class. This is to - # account for multi-line base-specifier lists, e.g.: - # class Derived - # : public Base { - end_class_head = class_info.starting_linenum - for i in range(class_info.starting_linenum, linenum): - if re.search(r"\{\s*$", clean_lines.lines[i]): - end_class_head = i - break - if end_class_head < linenum - 1: - error( - filename, - linenum, - "whitespace/blank_line", - 3, - f'"{matched.group(1)}:" should be preceded by a blank line', - ) - - -def GetPreviousNonBlankLine(clean_lines, linenum): - """Return the most recent non-blank line and its line number. - - Args: - clean_lines: A CleansedLines instance containing the file contents. - linenum: The number of the line to check. - - Returns: - A tuple with two elements. The first element is the contents of the last - non-blank line before the current line, or the empty string if this is the - first non-blank line. The second is the line number of that line, or -1 - if this is the first non-blank line. - """ - - prevlinenum = linenum - 1 - while prevlinenum >= 0: - prevline = clean_lines.elided[prevlinenum] - if not IsBlankLine(prevline): # if not a blank line... - return (prevline, prevlinenum) - prevlinenum -= 1 - return ("", -1) - - -def CheckBraces(filename, clean_lines, linenum, error): - """Looks for misplaced braces (e.g. at the end of line). - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[linenum] # get rid of comments and strings - - if re.match(r"\s*{\s*$", line): - # We allow an open brace to start a line in the case where someone is using - # braces in a block to explicitly create a new scope, which is commonly used - # to control the lifetime of stack-allocated variables. Braces are also - # used for brace initializers inside function calls. We don't detect this - # perfectly: we just don't complain if the last non-whitespace character on - # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the - # previous line starts a preprocessor block. We also allow a brace on the - # following line if it is part of an array initialization and would not fit - # within the 80 character limit of the preceding line. - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if ( - not re.search(r"[,;:}{(]\s*$", prevline) - and not re.match(r"\s*#", prevline) - and not (GetLineWidth(prevline) > _line_length - 2 and "[]" in prevline) - ): - error( - filename, - linenum, - "whitespace/braces", - 4, - "{ should almost always be at the end of the previous line", - ) - - # An else clause should be on the same line as the preceding closing brace. - if last_wrong := re.match(r"\s*else\b\s*(?:if\b|\{|$)", line): - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if re.match(r"\s*}\s*$", prevline): - error( - filename, - linenum, - "whitespace/newline", - 4, - "An else should appear on the same line as the preceding }", - ) - else: - last_wrong = False - - # If braces come on one side of an else, they should be on both. - # However, we have to worry about "else if" that spans multiple lines! - if re.search(r"else if\s*\(", line): # could be multi-line if - brace_on_left = bool(re.search(r"}\s*else if\s*\(", line)) - # find the ( after the if - pos = line.find("else if") - pos = line.find("(", pos) - if pos > 0: - (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) - brace_on_right = endline[endpos:].find("{") != -1 - if brace_on_left != brace_on_right: # must be brace after if - error( - filename, - linenum, - "readability/braces", - 5, - "If an else has a brace on one side, it should have it on both", - ) - # Prevent detection if statement has { and we detected an improper newline after } - elif re.search(r"}\s*else[^{]*$", line) or ( - re.match(r"[^}]*else\s*{", line) and not last_wrong - ): - error( - filename, - linenum, - "readability/braces", - 5, - "If an else has a brace on one side, it should have it on both", - ) - - # No control clauses with braces should have its contents on the same line - # Exclude } which will be covered by empty-block detect - # Exclude ; which may be used by while in a do-while - if ( - keyword := re.search( - r"\b(else if|if|while|for|switch)" # These have parens - r"\s*\(.*\)\s*(?:\[\[(?:un)?likely\]\]\s*)?{\s*[^\s\\};]", - line, - ) - ) or ( - keyword := re.search( - r"\b(else|do|try)" # These don't have parens - r"\s*(?:\[\[(?:un)?likely\]\]\s*)?{\s*[^\s\\}]", - line, - ) - ): - error( - filename, - linenum, - "whitespace/newline", - 5, - f"Controlled statements inside brackets of {keyword.group(1)} clause" - " should be on a separate line", - ) - - # TODO(aaronliu0130): Err on if...else and do...while statements without braces; - # style guide has changed since the below comment was written - - # Check single-line if/else bodies. The style guide says 'curly braces are not - # required for single-line statements'. We additionally allow multi-line, - # single statements, but we reject anything with more than one semicolon in - # it. This means that the first semicolon after the if should be at the end of - # its line, and the line after that should have an indent level equal to or - # lower than the if. We also check for ambiguous if/else nesting without - # braces. - if_else_match = re.search(r"\b(if\s*(|constexpr)\s*\(|else\b)", line) - if if_else_match and not re.match(r"\s*#", line): - if_indent = GetIndentLevel(line) - endline, endlinenum, endpos = line, linenum, if_else_match.end() - if_match = re.search(r"\bif\s*(|constexpr)\s*\(", line) - if if_match: - # This could be a multiline if condition, so find the end first. - pos = if_match.end() - 1 - (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) - # Check for an opening brace, either directly after the if or on the next - # line. If found, this isn't a single-statement conditional. - if not re.match(r"\s*(?:\[\[(?:un)?likely\]\]\s*)?{", endline[endpos:]) and not ( - re.match(r"\s*$", endline[endpos:]) - and endlinenum < (len(clean_lines.elided) - 1) - and re.match(r"\s*{", clean_lines.elided[endlinenum + 1]) - ): - while ( - endlinenum < len(clean_lines.elided) - and ";" not in clean_lines.elided[endlinenum][endpos:] - ): - endlinenum += 1 - endpos = 0 - if endlinenum < len(clean_lines.elided): - endline = clean_lines.elided[endlinenum] - # We allow a mix of whitespace and closing braces (e.g. for one-liner - # methods) and a single \ after the semicolon (for macros) - endpos = endline.find(";") - if not re.match(r";[\s}]*(\\?)$", endline[endpos:]): - # Semicolon isn't the last character, there's something trailing. - # Output a warning if the semicolon is not contained inside - # a lambda expression. - if not re.match(r"^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$", endline): - error( - filename, - linenum, - "readability/braces", - 4, - "If/else bodies with multiple statements require braces", - ) - elif endlinenum < len(clean_lines.elided) - 1: - # Make sure the next line is dedented - next_line = clean_lines.elided[endlinenum + 1] - next_indent = GetIndentLevel(next_line) - # With ambiguous nested if statements, this will error out on the - # if that *doesn't* match the else, regardless of whether it's the - # inner one or outer one. - if if_match and re.match(r"\s*else\b", next_line) and next_indent != if_indent: - error( - filename, - linenum, - "readability/braces", - 4, - "Else clause should be indented at the same level as if. " - "Ambiguous nested if/else chains require braces.", - ) - elif next_indent > if_indent: - error( - filename, - linenum, - "readability/braces", - 4, - "If/else bodies with multiple statements require braces", - ) - - -def CheckTrailingSemicolon(filename, clean_lines, linenum, error): - """Looks for redundant trailing semicolon. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[linenum] - - # Block bodies should not be followed by a semicolon. Due to C++11 - # brace initialization, there are more places where semicolons are - # required than not, so we explicitly list the allowed rules rather - # than listing the disallowed ones. These are the places where "};" - # should be replaced by just "}": - # 1. Some flavor of block following closing parenthesis: - # for (;;) {}; - # while (...) {}; - # switch (...) {}; - # Function(...) {}; - # if (...) {}; - # if (...) else if (...) {}; - # - # 2. else block: - # if (...) else {}; - # - # 3. const member function: - # Function(...) const {}; - # - # 4. Block following some statement: - # x = 42; - # {}; - # - # 5. Block at the beginning of a function: - # Function(...) { - # {}; - # } - # - # Note that naively checking for the preceding "{" will also match - # braces inside multi-dimensional arrays, but this is fine since - # that expression will not contain semicolons. - # - # 6. Block following another block: - # while (true) {} - # {}; - # - # 7. End of namespaces: - # namespace {}; - # - # These semicolons seems far more common than other kinds of - # redundant semicolons, possibly due to people converting classes - # to namespaces. For now we do not warn for this case. - # - # Try matching case 1 first. - match = re.match(r"^(.*\)\s*)\{", line) - if match: - # Matched closing parenthesis (case 1). Check the token before the - # matching opening parenthesis, and don't warn if it looks like a - # macro. This avoids these false positives: - # - macro that defines a base class - # - multi-line macro that defines a base class - # - macro that defines the whole class-head - # - # But we still issue warnings for macros that we know are safe to - # warn, specifically: - # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P - # - TYPED_TEST - # - INTERFACE_DEF - # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: - # - # We implement a list of safe macros instead of a list of - # unsafe macros, even though the latter appears less frequently in - # google code and would have been easier to implement. This is because - # the downside for getting the allowed checks wrong means some extra - # semicolons, while the downside for getting disallowed checks wrong - # would result in compile errors. - # - # In addition to macros, we also don't want to warn on - # - Compound literals - # - Lambdas - # - alignas specifier with anonymous structs - # - decltype - # - concepts (requires expression) - closing_brace_pos = match.group(1).rfind(")") - opening_parenthesis = ReverseCloseExpression(clean_lines, linenum, closing_brace_pos) - if opening_parenthesis[2] > -1: - line_prefix = opening_parenthesis[0][0 : opening_parenthesis[2]] - macro = re.search(r"\b([A-Z_][A-Z0-9_]*)\s*$", line_prefix) - func = re.match(r"^(.*\])\s*$", line_prefix) - if ( - ( - macro - and macro.group(1) - not in ( - "TEST", - "TEST_F", - "MATCHER", - "MATCHER_P", - "TYPED_TEST", - "EXCLUSIVE_LOCKS_REQUIRED", - "SHARED_LOCKS_REQUIRED", - "LOCKS_EXCLUDED", - "INTERFACE_DEF", - ) - ) - or (func and not re.search(r"\boperator\s*\[\s*\]", func.group(1))) - or re.search(r"\b(?:struct|union)\s+alignas\s*$", line_prefix) - or re.search(r"\bdecltype$", line_prefix) - or re.search(r"\brequires.*$", line_prefix) - or re.search(r"\s+=\s*$", line_prefix) - ): - match = None - if ( - match - and opening_parenthesis[1] > 1 - and re.search(r"\]\s*$", clean_lines.elided[opening_parenthesis[1] - 1]) - ): - # Multi-line lambda-expression - match = None - - else: - # Try matching cases 2-3. - match = re.match(r"^(.*(?:else|\)\s*const)\s*)\{", line) - if not match: - # Try matching cases 4-6. These are always matched on separate lines. - # - # Note that we can't simply concatenate the previous line to the - # current line and do a single match, otherwise we may output - # duplicate warnings for the blank line case: - # if (cond) { - # // blank line - # } - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and re.search(r"[;{}]\s*$", prevline): - match = re.match(r"^(\s*)\{", line) - - # Check matching closing brace - if match: - (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, len(match.group(1))) - if endpos > -1 and re.match(r"^\s*;", endline[endpos:]): - # Current {} pair is eligible for semicolon check, and we have found - # the redundant semicolon, output warning here. - # - # Note: because we are scanning forward for opening braces, and - # outputting warnings for the matching closing brace, if there are - # nested blocks with trailing semicolons, we will get the error - # messages in reversed order. - - # We need to check the line forward for NOLINT - raw_lines = clean_lines.raw_lines - ParseNolintSuppressions(filename, raw_lines[endlinenum - 1], endlinenum - 1, error) - ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum, error) - - error(filename, endlinenum, "readability/braces", 4, "You don't need a ; after a }") - - -def CheckEmptyBlockBody(filename, clean_lines, linenum, error): - """Look for empty loop/conditional body with only a single semicolon. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - # Search for loop keywords at the beginning of the line. Because only - # whitespaces are allowed before the keywords, this will also ignore most - # do-while-loops, since those lines should start with closing brace. - # - # We also check "if" blocks here, since an empty conditional block - # is likely an error. - line = clean_lines.elided[linenum] - if matched := re.match(r"\s*(for|while|if)\s*\(", line): - # Find the end of the conditional expression. - (end_line, end_linenum, end_pos) = CloseExpression(clean_lines, linenum, line.find("(")) - - # Output warning if what follows the condition expression is a semicolon. - # No warning for all other cases, including whitespace or newline, since we - # have a separate check for semicolons preceded by whitespace. - if end_pos >= 0 and re.match(r";", end_line[end_pos:]): - if matched.group(1) == "if": - error( - filename, - end_linenum, - "whitespace/empty_conditional_body", - 5, - "Empty conditional bodies should use {}", - ) - else: - error( - filename, - end_linenum, - "whitespace/empty_loop_body", - 5, - "Empty loop bodies should use {} or continue", - ) - - # Check for if statements that have completely empty bodies (no comments) - # and no else clauses. - if end_pos >= 0 and matched.group(1) == "if": - # Find the position of the opening { for the if statement. - # Return without logging an error if it has no brackets. - opening_linenum = end_linenum - opening_line_fragment = end_line[end_pos:] - # Loop until EOF or find anything that's not whitespace or opening {. - while not re.search(r"^\s*\{", opening_line_fragment): - if re.search(r"^(?!\s*$)", opening_line_fragment): - # Conditional has no brackets. - return - opening_linenum += 1 - if opening_linenum == len(clean_lines.elided): - # Couldn't find conditional's opening { or any code before EOF. - return - opening_line_fragment = clean_lines.elided[opening_linenum] - # Set opening_line (opening_line_fragment may not be entire opening line). - opening_line = clean_lines.elided[opening_linenum] - - # Find the position of the closing }. - opening_pos = opening_line_fragment.find("{") - if opening_linenum == end_linenum: - # We need to make opening_pos relative to the start of the entire line. - opening_pos += end_pos - (closing_line, closing_linenum, closing_pos) = CloseExpression( - clean_lines, opening_linenum, opening_pos - ) - if closing_pos < 0: - return - - # Now construct the body of the conditional. This consists of the portion - # of the opening line after the {, all lines until the closing line, - # and the portion of the closing line before the }. - if clean_lines.raw_lines[opening_linenum] != CleanseComments( - clean_lines.raw_lines[opening_linenum] - ): - # Opening line ends with a comment, so conditional isn't empty. - return - if closing_linenum > opening_linenum: - # Opening line after the {. Ignore comments here since we checked above. - bodylist = list(opening_line[opening_pos + 1 :]) - # All lines until closing line, excluding closing line, with comments. - bodylist.extend(clean_lines.raw_lines[opening_linenum + 1 : closing_linenum]) - # Closing line before the }. Won't (and can't) have comments. - bodylist.append(clean_lines.elided[closing_linenum][: closing_pos - 1]) - body = "\n".join(bodylist) - else: - # If statement has brackets and fits on a single line. - body = opening_line[opening_pos + 1 : closing_pos - 1] - - # Check if the body is empty - if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): - return - # The body is empty. Now make sure there's not an else clause. - current_linenum = closing_linenum - current_line_fragment = closing_line[closing_pos:] - # Loop until EOF or find anything that's not whitespace or else clause. - while re.search(r"^\s*$|^(?=\s*else)", current_line_fragment): - if re.search(r"^(?=\s*else)", current_line_fragment): - # Found an else clause, so don't log an error. - return - current_linenum += 1 - if current_linenum == len(clean_lines.elided): - break - current_line_fragment = clean_lines.elided[current_linenum] - - # The body is empty and there's no else clause until EOF or other code. - error( - filename, - end_linenum, - "whitespace/empty_if_body", - 4, - ("If statement had no body and no else clause"), - ) - - -def FindCheckMacro(line): - """Find a replaceable CHECK-like macro. - - Args: - line: line to search on. - Returns: - (macro name, start position), or (None, -1) if no replaceable - macro is found. - """ - for macro in _CHECK_MACROS: - i = line.find(macro) - if i >= 0: - # Find opening parenthesis. Do a regular expression match here - # to make sure that we are matching the expected CHECK macro, as - # opposed to some other macro that happens to contain the CHECK - # substring. - matched = re.match(r"^(.*\b" + macro + r"\s*)\(", line) - if not matched: - continue - return (macro, len(matched.group(1))) - return (None, -1) - - -def CheckCheck(filename, clean_lines, linenum, error): - """Checks the use of CHECK and EXPECT macros. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - # Decide the set of replacement macros that should be suggested - lines = clean_lines.elided - (check_macro, start_pos) = FindCheckMacro(lines[linenum]) - if not check_macro: - return - - # Find end of the boolean expression by matching parentheses - (last_line, end_line, end_pos) = CloseExpression(clean_lines, linenum, start_pos) - if end_pos < 0: - return - - # If the check macro is followed by something other than a - # semicolon, assume users will log their own custom error messages - # and don't suggest any replacements. - if not re.match(r"\s*;", last_line[end_pos:]): - return - - if linenum == end_line: - expression = lines[linenum][start_pos + 1 : end_pos - 1] - else: - expression = lines[linenum][start_pos + 1 :] - for i in range(linenum + 1, end_line): - expression += lines[i] - expression += last_line[0 : end_pos - 1] - - # Parse expression so that we can take parentheses into account. - # This avoids false positives for inputs like "CHECK((a < 4) == b)", - # which is not replaceable by CHECK_LE. - lhs = "" - rhs = "" - operator = None - while expression: - matched = re.match( - r"^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||" - r"==|!=|>=|>|<=|<|\()(.*)$", - expression, - ) - if matched: - token = matched.group(1) - if token == "(": - # Parenthesized operand - expression = matched.group(2) - (end, _) = FindEndOfExpressionInLine(expression, 0, ["("]) - if end < 0: - return # Unmatched parenthesis - lhs += "(" + expression[0:end] - expression = expression[end:] - elif token in ("&&", "||"): - # Logical and/or operators. This means the expression - # contains more than one term, for example: - # CHECK(42 < a && a < b); - # - # These are not replaceable with CHECK_LE, so bail out early. - return - elif token in ("<<", "<<=", ">>", ">>=", "->*", "->"): - # Non-relational operator - lhs += token - expression = matched.group(2) - else: - # Relational operator - operator = token - rhs = matched.group(2) - break - else: - # Unparenthesized operand. Instead of appending to lhs one character - # at a time, we do another regular expression match to consume several - # characters at once if possible. Trivial benchmark shows that this - # is more efficient when the operands are longer than a single - # character, which is generally the case. - matched = re.match(r"^([^-=!<>()&|]+)(.*)$", expression) - if not matched: - matched = re.match(r"^(\s*\S)(.*)$", expression) - if not matched: - break - lhs += matched.group(1) - expression = matched.group(2) - - # Only apply checks if we got all parts of the boolean expression - if not (lhs and operator and rhs): - return - - # Check that rhs do not contain logical operators. We already know - # that lhs is fine since the loop above parses out && and ||. - if rhs.find("&&") > -1 or rhs.find("||") > -1: - return - - # At least one of the operands must be a constant literal. This is - # to avoid suggesting replacements for unprintable things like - # CHECK(variable != iterator) - # - # The following pattern matches decimal, hex integers, strings, and - # characters (in that order). - lhs = lhs.strip() - rhs = rhs.strip() - match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' - if re.match(match_constant, lhs) or re.match(match_constant, rhs): - # Note: since we know both lhs and rhs, we can provide a more - # descriptive error message like: - # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) - # Instead of: - # Consider using CHECK_EQ instead of CHECK(a == b) - # - # We are still keeping the less descriptive message because if lhs - # or rhs gets long, the error message might become unreadable. - error( - filename, - linenum, - "readability/check", - 2, - f"Consider using {_CHECK_REPLACEMENT[check_macro][operator]}" - f" instead of {check_macro}(a {operator} b)", - ) - - -def CheckAltTokens(filename, clean_lines, linenum, error): - """Check alternative keywords being used in boolean expressions. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Avoid preprocessor lines - if re.match(r"^\s*#", line): - return - - # Last ditch effort to avoid multi-line comments. This will not help - # if the comment started before the current line or ended after the - # current line, but it catches most of the false positives. At least, - # it provides a way to workaround this warning for people who use - # multi-line comments in preprocessor macros. - # - # TODO(google): remove this once cpplint has better support for - # multi-line comments. - if line.find("/*") >= 0 or line.find("*/") >= 0: - return - - for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): - error( - filename, - linenum, - "readability/alt_tokens", - 2, - f"Use operator {_ALT_TOKEN_REPLACEMENT[match.group(2)]} instead of {match.group(2)}", - ) - - -def GetLineWidth(line): - """Determines the width of the line in column positions. - - Args: - line: A string, which may be a Unicode string. - - Returns: - The width of the line in column positions, accounting for Unicode - combining characters and wide characters. - """ - if isinstance(line, str): - width = 0 - for uc in unicodedata.normalize("NFC", line): - if unicodedata.east_asian_width(uc) in ("W", "F"): - width += 2 - elif not unicodedata.combining(uc): - # Issue 337 - # https://mail.python.org/pipermail/python-list/2012-August/628809.html - if (sys.version_info.major, sys.version_info.minor) <= (3, 2): - # https://github.com/python/cpython/blob/2.7/Include/unicodeobject.h#L81 - is_wide_build = sysconfig.get_config_var("Py_UNICODE_SIZE") >= 4 - # https://github.com/python/cpython/blob/2.7/Objects/unicodeobject.c#L564 - is_low_surrogate = 0xDC00 <= ord(uc) <= 0xDFFF - if not is_wide_build and is_low_surrogate: - width -= 1 - - width += 1 - return width - return len(line) - - -def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, error, cppvar=None): - """Checks rules from the 'C++ style rules' section of cppguide.html. - - Most of these rules are hard to test (naming, comment style), but we - do what we can. In particular we check for 2-space indents, line lengths, - tab usage, spaces inside code, etc. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - cppvar: The header guard variable returned by GetHeaderGuardCPPVar. - """ - - # Don't use "elided" lines here, otherwise we can't check commented lines. - # Don't want to use "raw" either, because we don't want to check inside C++11 - # raw strings, - raw_lines = clean_lines.lines_without_raw_strings - line = raw_lines[linenum] - prev = raw_lines[linenum - 1] if linenum > 0 else "" - - if line.find("\t") != -1: - error(filename, linenum, "whitespace/tab", 1, "Tab found; better to use spaces") - - # One or three blank spaces at the beginning of the line is weird; it's - # hard to reconcile that with 2-space indents. - # NOTE: here are the conditions rob pike used for his tests. Mine aren't - # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces - # if(RLENGTH > 20) complain = 0; - # if(match($0, " +(error|private|public|protected):")) complain = 0; - # if(match(prev, "&& *$")) complain = 0; - # if(match(prev, "\\|\\| *$")) complain = 0; - # if(match(prev, "[\",=><] *$")) complain = 0; - # if(match($0, " <<")) complain = 0; - # if(match(prev, " +for \\(")) complain = 0; - # if(prevodd && match(prevprev, " +for \\(")) complain = 0; - scope_or_label_pattern = r"\s*(?:public|private|protected|signals)(?:\s+(?:slots\s*)?)?:\s*\\?$" - classinfo = nesting_state.InnermostClass() - initial_spaces = 0 - cleansed_line = clean_lines.elided[linenum] - while initial_spaces < len(line) and line[initial_spaces] == " ": - initial_spaces += 1 - # There are certain situations we allow one space, notably for - # section labels, and also lines containing multi-line raw strings. - # We also don't check for lines that look like continuation lines - # (of lines ending in double quotes, commas, equals, or angle brackets) - # because the rules for how to indent those are non-trivial. - if ( - not re.search(r'[",=><] *$', prev) - and (initial_spaces in {1, 3}) - and not re.match(scope_or_label_pattern, cleansed_line) - and not (clean_lines.raw_lines[linenum] != line and re.match(r'^\s*""', line)) - ): - error( - filename, - linenum, - "whitespace/indent", - 3, - "Weird number of spaces at line-start. Are you using a 2-space indent?", - ) - - if line and line[-1].isspace(): - error( - filename, - linenum, - "whitespace/end_of_line", - 4, - "Line ends in whitespace. Consider deleting these extra spaces.", - ) - - # Check if the line is a header guard. - is_header_guard = IsHeaderExtension(file_extension) and line.startswith( - (f"#ifndef {cppvar}", f"#define {cppvar}", f"#endif // {cppvar}") - ) - # #include lines and header guards can be long, since there's no clean way to - # split them. - # - # URLs can be long too. It's possible to split these, but it makes them - # harder to cut&paste. - # - # The "$Id:...$" comment may also get very long without it being the - # developers fault. - # - # Doxygen documentation copying can get pretty long when using an overloaded - # function declaration - if ( - not line.startswith("#include") - and not is_header_guard - and not re.match(r"^\s*//.*http(s?)://\S*$", line) - and not re.match(r"^\s*//\s*[^\s]*$", line) - and not re.match(r"^// \$Id:.*#[0-9]+ \$$", line) - and not re.match(r"^\s*/// [@\\](copydoc|copydetails|copybrief) .*$", line) - ): - line_width = GetLineWidth(line) - if line_width > _line_length: - error( - filename, - linenum, - "whitespace/line_length", - 2, - f"Lines should be <= {_line_length} characters long", - ) - - if ( - cleansed_line.count(";") > 1 - and - # allow simple single line lambdas - not re.match(r"^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}\n\r]*\}", line) - and - # for loops are allowed two ;'s (and may run over two lines). - cleansed_line.find("for") == -1 - and ( - GetPreviousNonBlankLine(clean_lines, linenum)[0].find("for") == -1 - or GetPreviousNonBlankLine(clean_lines, linenum)[0].find(";") != -1 - ) - and - # It's ok to have many commands in a switch case that fits in 1 line - not ( - (cleansed_line.find("case ") != -1 or cleansed_line.find("default:") != -1) - and cleansed_line.find("break;") != -1 - ) - ): - error(filename, linenum, "whitespace/newline", 0, "More than one command on the same line") - - # Some more style checks - CheckBraces(filename, clean_lines, linenum, error) - CheckTrailingSemicolon(filename, clean_lines, linenum, error) - CheckEmptyBlockBody(filename, clean_lines, linenum, error) - CheckSpacing(filename, clean_lines, linenum, nesting_state, error) - CheckOperatorSpacing(filename, clean_lines, linenum, error) - CheckParenthesisSpacing(filename, clean_lines, linenum, error) - CheckCommaSpacing(filename, clean_lines, linenum, error) - CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) - CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) - CheckCheck(filename, clean_lines, linenum, error) - CheckAltTokens(filename, clean_lines, linenum, error) - classinfo = nesting_state.InnermostClass() - if classinfo: - CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) - - -_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') -# Matches the first component of a filename delimited by -s and _s. That is: -# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' -_RE_FIRST_COMPONENT = re.compile(r"^[^-_.]+") - - -def _DropCommonSuffixes(filename): - """Drops common suffixes like _test.cc or -inl.h from filename. - - For example: - >>> _DropCommonSuffixes('foo/foo-inl.h') - 'foo/foo' - >>> _DropCommonSuffixes('foo/bar/foo.cc') - 'foo/bar/foo' - >>> _DropCommonSuffixes('foo/foo_internal.h') - 'foo/foo' - >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') - 'foo/foo_unusualinternal' - - Args: - filename: The input filename. - - Returns: - The filename with the common suffix removed. - """ - for suffix in itertools.chain( - ( - f"{test_suffix.lstrip('_')}.{ext}" - for test_suffix, ext in itertools.product(_test_suffixes, GetNonHeaderExtensions()) - ), - ( - f"{suffix}.{ext}" - for suffix, ext in itertools.product(["inl", "imp", "internal"], GetHeaderExtensions()) - ), - ): - if ( - filename.endswith(suffix) - and len(filename) > len(suffix) - and filename[-len(suffix) - 1] in ("-", "_") - ): - return filename[: -len(suffix) - 1] - return os.path.splitext(filename)[0] - - -def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="default"): - """Figures out what kind of header 'include' is. - - Args: - fileinfo: The current file cpplint is running over. A FileInfo instance. - include: The path to a #included file. - used_angle_brackets: True if the #include used <> rather than "". - include_order: "default" or other value allowed in program arguments - - Returns: - One of the _XXX_HEADER constants. - - For example: - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) - _C_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) - _CPP_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', True, "standardcfirst") - _OTHER_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) - _LIKELY_MY_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), - ... 'bar/foo_other_ext.h', False) - _POSSIBLE_MY_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) - _OTHER_HEADER - """ - # This is a list of all standard c++ header files, except - # those already checked for above. - is_cpp_header = include in _CPP_HEADERS - - # Mark include as C header if in list or in a known folder for standard-ish C headers. - is_std_c_header = (include_order == "default") or ( - include in _C_HEADERS - # additional linux glibc header folders - or re.search(rf"(?:{'|'.join(C_STANDARD_HEADER_FOLDERS)})\/.*\.h", include) - ) - - # Headers with C++ extensions shouldn't be considered C system headers - include_ext = os.path.splitext(include)[1] - is_system = used_angle_brackets and include_ext not in [".hh", ".hpp", ".hxx", ".h++"] - - if is_system: - if is_cpp_header: - return _CPP_SYS_HEADER - if is_std_c_header: - return _C_SYS_HEADER - return _OTHER_SYS_HEADER - - # If the target file and the include we're checking share a - # basename when we drop common extensions, and the include - # lives in . , then it's likely to be owned by the target file. - target_dir, target_base = os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName())) - include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) - target_dir_pub = os.path.normpath(target_dir + "/../public") - target_dir_pub = target_dir_pub.replace("\\", "/") - if target_base == include_base and (include_dir in (target_dir, target_dir_pub)): - return _LIKELY_MY_HEADER - - # If the target and include share some initial basename - # component, it's possible the target is implementing the - # include, so it's allowed to be first, but we'll never - # complain if it's not there. - target_first_component = _RE_FIRST_COMPONENT.match(target_base) - include_first_component = _RE_FIRST_COMPONENT.match(include_base) - if ( - target_first_component - and include_first_component - and target_first_component.group(0) == include_first_component.group(0) - ): - return _POSSIBLE_MY_HEADER - - return _OTHER_HEADER - - -def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): - """Check rules that are applicable to #include lines. - - Strings on #include lines are NOT removed from elided line, to make - certain tasks easier. However, to prevent false positives, checks - applicable to #include lines in CheckLanguage must be put here. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - include_state: An _IncludeState instance in which the headers are inserted. - error: The function to call with any errors found. - """ - fileinfo = FileInfo(filename) - line = clean_lines.lines[linenum] - - # "include" should use the new style "foo/bar.h" instead of just "bar.h" - # Only do this check if the included header follows google naming - # conventions. If not, assume that it's a 3rd party API that - # requires special include conventions. - # - # We also make an exception for Lua headers, which follow google - # naming convention but not the include convention. - match = re.match(r'#include\s*"([^/]+\.(.*))"', line) - if ( - match - and IsHeaderExtension(match.group(2)) - and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)) - ): - error( - filename, - linenum, - "build/include_subdir", - 4, - "Include the directory when naming header files", - ) - - # we shouldn't include a file more than once. actually, there are a - # handful of instances where doing so is okay, but in general it's - # not. - match = _RE_PATTERN_INCLUDE.search(line) - if match: - include = match.group(2) - used_angle_brackets = match.group(1) == "<" - duplicate_line = include_state.FindHeader(include) - if duplicate_line >= 0: - error( - filename, - linenum, - "build/include", - 4, - f'"{include}" already included at {filename}:{duplicate_line}', - ) - return - - for extension in GetNonHeaderExtensions(): - if include.endswith("." + extension) and os.path.dirname( - fileinfo.RepositoryName() - ) != os.path.dirname(include): - error( - filename, - linenum, - "build/include", - 4, - "Do not include ." + extension + " files from other packages", - ) - return - - # We DO want to include a 3rd party looking header if it matches the - # filename. Otherwise we get an erroneous error "...should include its - # header" error later. - third_src_header = False - for ext in GetHeaderExtensions(): - basefilename = filename[0 : len(filename) - len(fileinfo.Extension())] - headerfile = basefilename + "." + ext - headername = FileInfo(headerfile).RepositoryName() - if headername in include or include in headername: - third_src_header = True - break - - if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include): - include_state.include_list[-1].append((include, linenum)) - - # We want to ensure that headers appear in the right order: - # 1) for foo.cc, foo.h (preferred location) - # 2) c system files - # 3) cpp system files - # 4) for foo.cc, foo.h (deprecated location) - # 5) other google headers - # - # We classify each include statement as one of those 5 types - # using a number of techniques. The include_state object keeps - # track of the highest type seen, and complains if we see a - # lower type after that. - error_message = include_state.CheckNextIncludeOrder( - _ClassifyInclude(fileinfo, include, used_angle_brackets, _include_order) - ) - if error_message: - error( - filename, - linenum, - "build/include_order", - 4, - f"{error_message}. Should be: {fileinfo.BaseName()}.h, c system," - " c++ system, other.", - ) - canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) - if not include_state.IsInAlphabeticalOrder(clean_lines, linenum, canonical_include): - error( - filename, - linenum, - "build/include_alpha", - 4, - f'Include "{include}" not in alphabetical order', - ) - include_state.SetLastHeader(canonical_include) - - -def _GetTextInside(text, start_pattern): - r"""Retrieves all the text between matching open and close parentheses. - - Given a string of lines and a regular expression string, retrieve all the text - following the expression and between opening punctuation symbols like - (, [, or {, and the matching close-punctuation symbol. This properly nested - occurrences of the punctuation, so for the text like - printf(a(), b(c())); - a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. - start_pattern must match string having an open punctuation symbol at the end. - - Args: - text: The lines to extract text. Its comments and strings must be elided. - It can be single line and can span multiple lines. - start_pattern: The regexp string indicating where to start extracting - the text. - Returns: - The extracted text. - None if either the opening string or ending punctuation could not be found. - """ - # TODO(google): Audit cpplint.py to see what places could be profitably - # rewritten to use _GetTextInside (and use inferior regexp matching today). - - # Give opening punctuation to get the matching close-punctuation. - matching_punctuation = {"(": ")", "{": "}", "[": "]"} - closing_punctuation = set(dict.values(matching_punctuation)) - - # Find the position to start extracting text. - match = re.search(start_pattern, text, re.MULTILINE) - if not match: # start_pattern not found in text. - return None - start_position = match.end(0) - - assert start_position > 0, "start_pattern must ends with an opening punctuation." - assert text[start_position - 1] in matching_punctuation, ( - "start_pattern must ends with an opening punctuation." - ) - # Stack of closing punctuation we expect to have in text after position. - punctuation_stack = [matching_punctuation[text[start_position - 1]]] - position = start_position - while punctuation_stack and position < len(text): - if text[position] == punctuation_stack[-1]: - punctuation_stack.pop() - elif text[position] in closing_punctuation: - # A closing punctuation without matching opening punctuation. - return None - elif text[position] in matching_punctuation: - punctuation_stack.append(matching_punctuation[text[position]]) - position += 1 - if punctuation_stack: - # Opening punctuation left without matching close-punctuation. - return None - # punctuation match. - return text[start_position : position - 1] - - -# Patterns for matching call-by-reference parameters. -# -# Supports nested templates up to 2 levels deep using this messy pattern: -# < (?: < (?: < [^<>]* -# > -# | [^<>] )* -# > -# | [^<>] )* -# > -_RE_PATTERN_IDENT = r"[_a-zA-Z]\w*" # =~ [[:alpha:]][[:alnum:]]* -_RE_PATTERN_TYPE = ( - r"(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?" - r"(?:\w|" - r"\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|" - r"::)+" -) -# A call-by-reference parameter ends with '& identifier'. -_RE_PATTERN_REF_PARAM = re.compile( - r"(" + _RE_PATTERN_TYPE + r"(?:\s*(?:\bconst\b|[*]))*\s*" - r"&\s*" + _RE_PATTERN_IDENT + r")\s*(?:=[^,()]+)?[,)]" -) -# A call-by-const-reference parameter either ends with 'const& identifier' -# or looks like 'const type& identifier' when 'type' is atomic. -_RE_PATTERN_CONST_REF_PARAM = ( - r"(?:.*\s*\bconst\s*&\s*" - + _RE_PATTERN_IDENT - + r"|const\s+" - + _RE_PATTERN_TYPE - + r"\s*&\s*" - + _RE_PATTERN_IDENT - + r")" -) -# Stream types. -_RE_PATTERN_REF_STREAM_PARAM = r"(?:.*stream\s*&\s*" + _RE_PATTERN_IDENT + r")" - - -def CheckLanguage( - filename, clean_lines, linenum, file_extension, include_state, nesting_state, error -): - """Checks rules from the 'C++ language rules' section of cppguide.html. - - Some of these rules are hard to test (function overloading, using - uint32_t inappropriately), but we do the best we can. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - include_state: An _IncludeState instance in which the headers are inserted. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - # If the line is empty or consists of entirely a comment, no need to - # check it. - line = clean_lines.elided[linenum] - if not line: - return - - match = _RE_PATTERN_INCLUDE.search(line) - if match: - CheckIncludeLine(filename, clean_lines, linenum, include_state, error) - return - - # Reset include state across preprocessor directives. This is meant - # to silence warnings for conditional includes. - match = re.match(r"^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b", line) - if match: - include_state.ResetSection(match.group(1)) - - # Perform other checks now that we are sure that this is not an include line - CheckCasts(filename, clean_lines, linenum, error) - CheckGlobalStatic(filename, clean_lines, linenum, error) - CheckPrintf(filename, clean_lines, linenum, error) - - if IsHeaderExtension(file_extension): - # TODO(google): check that 1-arg constructors are explicit. - # How to tell it's a constructor? - # (handled in CheckForNonStandardConstructs for now) - # TODO(google): check that classes declare or disable copy/assign - # (level 1 error) - pass - - # Check if people are using the verboten C basic types. The only exception - # we regularly allow is "unsigned short port" for port. - if re.search(r"\bshort port\b", line): - if not re.search(r"\bunsigned short port\b", line): - error( - filename, linenum, "runtime/int", 4, 'Use "unsigned short" for ports, not "short"' - ) - else: - match = re.search(r"\b(short|long(?! +double)|long long)\b", line) - if match: - error( - filename, - linenum, - "runtime/int", - 4, - f"Use int16_t/int64_t/etc, rather than the C type {match.group(1)}", - ) - - # Check if some verboten operator overloading is going on - # TODO(google): catch out-of-line unary operator&: - # class X {}; - # int operator&(const X& x) { return 42; } // unary operator& - # The trick is it's hard to tell apart from binary operator&: - # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& - if re.search(r"\boperator\s*&\s*\(\s*\)", line): - error( - filename, - linenum, - "runtime/operator", - 4, - "Unary operator& is dangerous. Do not use it.", - ) - - # Check for suspicious usage of "if" like - # } if (a == b) { - if re.search(r"\}\s*if\s*\(", line): - error( - filename, - linenum, - "readability/braces", - 4, - 'Did you mean "else if"? If not, start a new line for "if".', - ) - - # Check for potential format string bugs like printf(foo). - # We constrain the pattern not to pick things like DocidForPrintf(foo). - # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) - # TODO(google): Catch the following case. Need to change the calling - # convention of the whole function to process multiple line to handle it. - # printf( - # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); - if printf_args := _GetTextInside(line, r"(?i)\b(string)?printf\s*\("): - match = re.match(r"([\w.\->()]+)$", printf_args) - if match and match.group(1) != "__VA_ARGS__": - function_name = re.search(r"\b((?:string)?printf)\s*\(", line, re.IGNORECASE).group(1) - error( - filename, - linenum, - "runtime/printf", - 4, - f'Potential format string bug. Do {function_name}("%s", {match.group(1)}) instead.', - ) - - # Check for potential memset bugs like memset(buf, sizeof(buf), 0). - match = re.search(r"memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)", line) - if match and not re.match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): - error( - filename, - linenum, - "runtime/memset", - 4, - f'Did you mean "memset({match.group(1)}, 0, {match.group(2)})"?', - ) - - if re.search(r"\busing namespace\b", line): - if re.search(r"\bliterals\b", line): - error( - filename, - linenum, - "build/namespaces_literals", - 5, - "Do not use namespace using-directives. Use using-declarations instead.", - ) - else: - error( - filename, - linenum, - "build/namespaces", - 5, - "Do not use namespace using-directives. Use using-declarations instead.", - ) - - # Detect variable-length arrays. - match = re.match(r"\s*(.+::)?(\w+) [a-z]\w*\[(.+)];", line) - if ( - match - and match.group(2) != "return" - and match.group(2) != "delete" - and match.group(3).find("]") == -1 - ): - # Split the size using space and arithmetic operators as delimiters. - # If any of the resulting tokens are not compile time constants then - # report the error. - tokens = re.split(r"\s|\+|\-|\*|\/|<<|>>]", match.group(3)) - is_const = True - skip_next = False - for tok in tokens: - if skip_next: - skip_next = False - continue - - if re.search(r"sizeof\(.+\)", tok): - continue - if re.search(r"arraysize\(\w+\)", tok): - continue - - tok = tok.lstrip("(") - tok = tok.rstrip(")") - if not tok: - continue - if re.match(r"\d+", tok): - continue - if re.match(r"0[xX][0-9a-fA-F]+", tok): - continue - if re.match(r"k[A-Z0-9]\w*", tok): - continue - if re.match(r"(.+::)?k[A-Z0-9]\w*", tok): - continue - if re.match(r"(.+::)?[A-Z][A-Z0-9_]*", tok): - continue - # A catch all for tricky sizeof cases, including 'sizeof expression', - # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' - # requires skipping the next token because we split on ' ' and '*'. - if tok.startswith("sizeof"): - skip_next = True - continue - is_const = False - break - if not is_const: - error( - filename, - linenum, - "runtime/arrays", - 1, - "Do not use variable-length arrays. Use an appropriately named " - "('k' followed by CamelCase) compile-time constant for the size.", - ) - - # Check for use of unnamed namespaces in header files. Registration - # macros are typically OK, so we allow use of "namespace {" on lines - # that end with backslashes. - if ( - IsHeaderExtension(file_extension) - and re.search(r"\bnamespace\s*{", line) - and line[-1] != "\\" - ): - error( - filename, - linenum, - "build/namespaces_headers", - 4, - "Do not use unnamed namespaces in header files. See " - "https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces" - " for more information.", - ) - - -def CheckGlobalStatic(filename, clean_lines, linenum, error): - """Check for unsafe global or static objects. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Match two lines at a time to support multiline declarations - if linenum + 1 < clean_lines.NumLines() and not re.search(r"[;({]", line): - line += clean_lines.elided[linenum + 1].strip() - - # Check for people declaring static/global STL strings at the top level. - # This is dangerous because the C++ language does not guarantee that - # globals with constructors are initialized before the first access, and - # also because globals can be destroyed when some threads are still running. - # TODO(google): Generalize this to also find static unique_ptr instances. - # TODO(google): File bugs for clang-tidy to find these. - match = re.match( - r"((?:|static +)(?:|const +))(?::*std::)?string( +const)? +" - r"([a-zA-Z0-9_:]+)\b(.*)", - line, - ) - - # Remove false positives: - # - String pointers (as opposed to values). - # string *pointer - # const string *pointer - # string const *pointer - # string *const pointer - # - # - Functions and template specializations. - # string Function(... - # string Class::Method(... - # - # - Operators. These are matched separately because operator names - # cross non-word boundaries, and trying to match both operators - # and functions at the same time would decrease accuracy of - # matching identifiers. - # string Class::operator*() - if ( - match - and not re.search(r"\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w", line) - and not re.search(r"\boperator\W", line) - and not re.match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4)) - ): - if re.search(r"\bconst\b", line): - error( - filename, - linenum, - "runtime/string", - 4, - "For a static/global string constant, use a C style string instead:" - f' "{match.group(1)}char{match.group(2) or ""} {match.group(3)}[]".', - ) - else: - error( - filename, - linenum, - "runtime/string", - 4, - "Static/global string variables are not permitted.", - ) - - if re.search(r"\b([A-Za-z0-9_]*_)\(\1\)", line) or re.search( - r"\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)", line - ): - error( - filename, - linenum, - "runtime/init", - 4, - "You seem to be initializing a member variable with itself.", - ) - - -def CheckPrintf(filename, clean_lines, linenum, error): - """Check for printf related issues. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # When snprintf is used, the second argument shouldn't be a literal. - match = re.search(r"snprintf\s*\(([^,]*),\s*([0-9]*)\s*,", line) - if match and match.group(2) != "0": - # If 2nd arg is zero, snprintf is used to calculate size. - error( - filename, - linenum, - "runtime/printf", - 3, - "If you can, use" - f" sizeof({match.group(1)}) instead of {match.group(2)}" - " as the 2nd arg to snprintf.", - ) - - # Check if some verboten C functions are being used. - if re.search(r"\bsprintf\s*\(", line): - error(filename, linenum, "runtime/printf", 5, "Never use sprintf. Use snprintf instead.") - match = re.search(r"\b(strcpy|strcat)\s*\(", line) - if match: - error( - filename, - linenum, - "runtime/printf", - 4, - f"Almost always, snprintf is better than {match.group(1)}", - ) - - -def IsDerivedFunction(clean_lines, linenum): - """Check if current line contains an inherited function. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line contains a function with "override" - virt-specifier. - """ - # Scan back a few lines for start of current function - for i in range(linenum, max(-1, linenum - 10), -1): - match = re.match(r"^([^()]*\w+)\(", clean_lines.elided[i]) - if match: - # Look for "override" after the matching closing parenthesis - line, _, closing_paren = CloseExpression(clean_lines, i, len(match.group(1))) - return closing_paren >= 0 and re.search(r"\boverride\b", line[closing_paren:]) - return False - - -def IsOutOfLineMethodDefinition(clean_lines, linenum): - """Check if current line contains an out-of-line method definition. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line contains an out-of-line method definition. - """ - # Scan back a few lines for start of current function - for i in range(linenum, max(-1, linenum - 10), -1): - if re.match(r"^([^()]*\w+)\(", clean_lines.elided[i]): - return re.match(r"^[^()]*\w+::\w+\(", clean_lines.elided[i]) is not None - return False - - -def IsInitializerList(clean_lines, linenum): - """Check if current line is inside constructor initializer list. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line appears to be inside constructor initializer - list, False otherwise. - """ - for i in range(linenum, 1, -1): - line = clean_lines.elided[i] - if i == linenum: - remove_function_body = re.match(r"^(.*)\{\s*$", line) - if remove_function_body: - line = remove_function_body.group(1) - - if re.search(r"\s:\s*\w+[({]", line): - # A lone colon tend to indicate the start of a constructor - # initializer list. It could also be a ternary operator, which - # also tend to appear in constructor initializer lists as - # opposed to parameter lists. - return True - if re.search(r"\}\s*,\s*$", line): - # A closing brace followed by a comma is probably the end of a - # brace-initialized member in constructor initializer list. - return True - if re.search(r"[{};]\s*$", line): - # Found one of the following: - # - A closing brace or semicolon, probably the end of the previous - # function. - # - An opening brace, probably the start of current class or namespace. - # - # Current line is probably not inside an initializer list since - # we saw one of those things without seeing the starting colon. - return False - - # Got to the beginning of the file without seeing the start of - # constructor initializer list. - return False - - -def CheckForNonConstReference(filename, clean_lines, linenum, nesting_state, error): - """Check for non-const references. - - Separate from CheckLanguage since it scans backwards from current - line, instead of scanning forward. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - # Do nothing if there is no '&' on current line. - line = clean_lines.elided[linenum] - if "&" not in line: - return - - # If a function is inherited, current function doesn't have much of - # a choice, so any non-const references should not be blamed on - # derived function. - if IsDerivedFunction(clean_lines, linenum): - return - - # Don't warn on out-of-line method definitions, as we would warn on the - # in-line declaration, if it isn't marked with 'override'. - if IsOutOfLineMethodDefinition(clean_lines, linenum): - return - - # Long type names may be broken across multiple lines, usually in one - # of these forms: - # LongType - # ::LongTypeContinued &identifier - # LongType:: - # LongTypeContinued &identifier - # LongType< - # ...>::LongTypeContinued &identifier - # - # If we detected a type split across two lines, join the previous - # line to current line so that we can match const references - # accordingly. - # - # Note that this only scans back one line, since scanning back - # arbitrary number of lines would be expensive. If you have a type - # that spans more than 2 lines, please use a typedef. - if linenum > 1: - previous = None - if re.match(r"\s*::(?:[\w<>]|::)+\s*&\s*\S", line): - # previous_line\n + ::current_line - previous = re.search( - r"\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$", clean_lines.elided[linenum - 1] - ) - elif re.match(r"\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S", line): - # previous_line::\n + current_line - previous = re.search( - r"\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$", clean_lines.elided[linenum - 1] - ) - if previous: - line = previous.group(1) + line.lstrip() - else: - # Check for templated parameter that is split across multiple lines - endpos = line.rfind(">") - if endpos > -1: - (_, startline, startpos) = ReverseCloseExpression(clean_lines, linenum, endpos) - if startpos > -1 and startline < linenum: - # Found the matching < on an earlier line, collect all - # pieces up to current line. - line = "" - for i in range(startline, linenum + 1): - line += clean_lines.elided[i].strip() - - # Check for non-const references in function parameters. A single '&' may - # found in the following places: - # inside expression: binary & for bitwise AND - # inside expression: unary & for taking the address of something - # inside declarators: reference parameter - # We will exclude the first two cases by checking that we are not inside a - # function body, including one that was just introduced by a trailing '{'. - # TODO(google): Doesn't account for 'catch(Exception& e)' [rare]. - if nesting_state.previous_stack_top and not ( - isinstance(nesting_state.previous_stack_top, (_ClassInfo, _NamespaceInfo)) - ): - # Not at toplevel, not within a class, and not within a namespace - return - - # Avoid initializer lists. We only need to scan back from the - # current line for something that starts with ':'. - # - # We don't need to check the current line, since the '&' would - # appear inside the second set of parentheses on the current line as - # opposed to the first set. - if linenum > 0: - for i in range(linenum - 1, max(0, linenum - 10), -1): - previous_line = clean_lines.elided[i] - if not re.search(r"[),]\s*$", previous_line): - break - if re.match(r"^\s*:\s+\S", previous_line): - return - - # Avoid preprocessors - if re.search(r"\\\s*$", line): - return - - # Avoid constructor initializer lists - if IsInitializerList(clean_lines, linenum): - return - - # We allow non-const references in a few standard places, like functions - # called "swap()" or iostream operators like "<<" or ">>". Do not check - # those function parameters. - # - # We also accept & in static_assert, which looks like a function but - # it's actually a declaration expression. - allowed_functions = ( - r"(?:[sS]wap(?:<\w:+>)?|" - r"operator\s*[<>][<>]|" - r"static_assert|COMPILE_ASSERT" - r")\s*\(" - ) - if re.search(allowed_functions, line): - return - if not re.search(r"\S+\([^)]*$", line): - # Don't see an allowed function on this line. Actually we - # didn't see any function name on this line, so this is likely a - # multi-line parameter list. Try a bit harder to catch this case. - for i in range(2): - if linenum > i and re.search(allowed_functions, clean_lines.elided[linenum - i - 1]): - return - - decls = re.sub(r"{[^}]*}", " ", line) # exclude function body - for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): - if not re.match(_RE_PATTERN_CONST_REF_PARAM, parameter) and not re.match( - _RE_PATTERN_REF_STREAM_PARAM, parameter - ): - error( - filename, - linenum, - "runtime/references", - 2, - "Is this a non-const reference? " - "If so, make const or use a pointer: " + re.sub(" *<", "<", parameter), - ) - - -def CheckCasts(filename, clean_lines, linenum, error): - """Various cast related checks. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Check to see if they're using an conversion function cast. - # I just try to capture the most common basic types, though there are more. - # Parameterless conversion functions, such as bool(), are allowed as they are - # probably a member operator declaration or default constructor. - match = re.search( - r"(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b" - r"(int|float|double|bool|char|int16_t|uint16_t|int32_t|uint32_t|int64_t|uint64_t)" - r"(\([^)].*)", - line, - ) - expecting_function = ExpectingFunctionArgs(clean_lines, linenum) - if match and not expecting_function: - matched_type = match.group(2) - - # matched_new_or_template is used to silence two false positives: - # - New operators - # - Template arguments with function types - # - # For template arguments, we match on types immediately following - # an opening bracket without any spaces. This is a fast way to - # silence the common case where the function type is the first - # template argument. False negative with less-than comparison is - # avoided because those operators are usually followed by a space. - # - # function // bracket + no space = false positive - # value < double(42) // bracket + space = true positive - matched_new_or_template = match.group(1) - - # Avoid arrays by looking for brackets that come after the closing - # parenthesis. - if re.match(r"\([^()]+\)\s*\[", match.group(3)): - return - - # Other things to ignore: - # - Function pointers - # - Casts to pointer types - # - Placement new - # - Alias declarations - matched_funcptr = match.group(3) - if ( - matched_new_or_template is None - and not ( - matched_funcptr - and ( - re.match(r"\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(", matched_funcptr) - or matched_funcptr.startswith("(*)") - ) - ) - and not re.match(r"\s*using\s+\S+\s*=\s*" + matched_type, line) - and not re.search(r"new\(\S+\)\s*" + matched_type, line) - ): - error( - filename, - linenum, - "readability/casting", - 4, - f"Using deprecated casting style. Use static_cast<{matched_type}>(...) instead", - ) - - if not expecting_function: - CheckCStyleCast( - filename, - clean_lines, - linenum, - "static_cast", - r"\((int|float|double|bool|char|u?int(16|32|64)_t|size_t)\)", - error, - ) - - # This doesn't catch all cases. Consider (const char * const)"hello". - # - # (char *) "foo" should always be a const_cast (reinterpret_cast won't - # compile). - if CheckCStyleCast( - filename, clean_lines, linenum, "const_cast", r'\((char\s?\*+\s?)\)\s*"', error - ): - pass - else: - # Check pointer casts for other than string constants - CheckCStyleCast( - filename, clean_lines, linenum, "reinterpret_cast", r"\((\w+\s?\*+\s?)\)", error - ) - - # In addition, we look for people taking the address of a cast. This - # is dangerous -- casts can assign to temporaries, so the pointer doesn't - # point where you think. - # - # Some non-identifier character is required before the '&' for the - # expression to be recognized as a cast. These are casts: - # expression = &static_cast(temporary()); - # function(&(int*)(temporary())); - # - # This is not a cast: - # reference_type&(int* function_param); - match = re.search( - r"(?:[^\w]&\(([^)*][^)]*)\)[\w(])|" - r"(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)", - line, - ) - if match: - # Try a better error message when the & is bound to something - # dereferenced by the casted pointer, as opposed to the casted - # pointer itself. - parenthesis_error = False - match = re.match(r"^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<", line) - if match: - _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) - if x1 >= 0 and clean_lines.elided[y1][x1] == "(": - _, y2, x2 = CloseExpression(clean_lines, y1, x1) - if x2 >= 0: - extended_line = clean_lines.elided[y2][x2:] - if y2 < clean_lines.NumLines() - 1: - extended_line += clean_lines.elided[y2 + 1] - if re.match(r"\s*(?:->|\[)", extended_line): - parenthesis_error = True - - if parenthesis_error: - error( - filename, - linenum, - "readability/casting", - 4, - ( - "Are you taking an address of something dereferenced " - "from a cast? Wrapping the dereferenced expression in " - "parentheses will make the binding more obvious" - ), - ) - else: - error( - filename, - linenum, - "runtime/casting", - 4, - ( - "Are you taking an address of a cast? " - "This is dangerous: could be a temp var. " - "Take the address before doing the cast, rather than after" - ), - ) - - -def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): - """Checks for a C-style cast by looking for the pattern. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - cast_type: The string for the C++ cast to recommend. This is either - reinterpret_cast, static_cast, or const_cast, depending. - pattern: The regular expression used to find C-style casts. - error: The function to call with any errors found. - - Returns: - True if an error was emitted. - False otherwise. - """ - line = clean_lines.elided[linenum] - match = re.search(pattern, line) - if not match: - return False - - # Exclude lines with keywords that tend to look like casts - context = line[0 : match.start(1) - 1] - if re.match(r".*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$", context): - return False - - # Try expanding current context to see if we one level of - # parentheses inside a macro. - if linenum > 0: - for i in range(linenum - 1, max(0, linenum - 5), -1): - context = clean_lines.elided[i] + context - if re.match(r".*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$", context): - return False - - # operator++(int) and operator--(int) - if context.endswith((" operator++", " operator--", "::operator++", "::operator--")): - return False - - # A single unnamed argument for a function tends to look like old style cast. - # If we see those, don't issue warnings for deprecated casts. - remainder = line[match.end(0) :] - if re.match(r"^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)", remainder): - return False - - # At this point, all that should be left is actual casts. - error( - filename, - linenum, - "readability/casting", - 4, - f"Using C-style cast. Use {cast_type}<{match.group(1)}>(...) instead", - ) - - return True - - -def ExpectingFunctionArgs(clean_lines, linenum): - """Checks whether where function type arguments are expected. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - - Returns: - True if the line at 'linenum' is inside something that expects arguments - of function types. - """ - line = clean_lines.elided[linenum] - return re.match(r"^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(", line) or ( - linenum >= 2 - and ( - re.match( - r"^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$", - clean_lines.elided[linenum - 1], - ) - or re.match( - r"^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$", clean_lines.elided[linenum - 2] - ) - or re.search(r"\bstd::m?function\s*\<\s*$", clean_lines.elided[linenum - 1]) - ) - ) - - -_HEADERS_CONTAINING_TEMPLATES: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("", ("deque",)), - ( - "", - ( - "unary_function", - "binary_function", - "plus", - "minus", - "multiplies", - "divides", - "modulus", - "negate", - "equal_to", - "not_equal_to", - "greater", - "less", - "greater_equal", - "less_equal", - "logical_and", - "logical_or", - "logical_not", - "unary_negate", - "not1", - "binary_negate", - "not2", - "bind1st", - "bind2nd", - "pointer_to_unary_function", - "pointer_to_binary_function", - "ptr_fun", - "mem_fun_t", - "mem_fun", - "mem_fun1_t", - "mem_fun1_ref_t", - "mem_fun_ref_t", - "const_mem_fun_t", - "const_mem_fun1_t", - "const_mem_fun_ref_t", - "const_mem_fun1_ref_t", - "mem_fun_ref", - ), - ), - ("", ("numeric_limits",)), - ("", ("list",)), - ("", ("multimap",)), - ( - "", - ("allocator", "make_shared", "make_unique", "shared_ptr", "unique_ptr", "weak_ptr"), - ), - ( - "", - ( - "queue", - "priority_queue", - ), - ), - ( - "", - ( - "set", - "multiset", - ), - ), - ("", ("stack",)), - ( - "", - ( - "char_traits", - "basic_string", - ), - ), - ("", ("tuple",)), - ("", ("unordered_map", "unordered_multimap")), - ("", ("unordered_set", "unordered_multiset")), - ("", ("pair",)), - ("", ("vector",)), - # gcc extensions. - # Note: std::hash is their hash, ::hash is our hash - ( - "", - ( - "hash_map", - "hash_multimap", - ), - ), - ( - "", - ( - "hash_set", - "hash_multiset", - ), - ), - ("", ("slist",)), -) - -_HEADERS_MAYBE_TEMPLATES: tuple[tuple[str, tuple[str, ...]], ...] = ( - ( - "", - ( - "copy", - "max", - "min", - "min_element", - "sort", - "transform", - ), - ), - ("", ("forward", "make_pair", "move", "swap")), -) - -# Non templated types or global objects -_HEADERS_TYPES_OR_OBJS: tuple[tuple[str, tuple[str, ...]], ...] = ( - # String and others are special -- it is a non-templatized type in STL. - ("", ("string",)), - ("", ("cin", "cout", "cerr", "clog", "wcin", "wcout", "wcerr", "wclog")), - ("", ("FILE", "fpos_t")), -) - -# Non templated functions -_HEADERS_FUNCTIONS: tuple[tuple[str, tuple[str, ...]], ...] = ( - ( - "", - ( - "fopen", - "freopen", - "fclose", - "fflush", - "setbuf", - "setvbuf", - "fread", - "fwrite", - "fgetc", - "getc", - "fgets", - "fputc", - "putc", - "fputs", - "getchar", - "gets", - "putchar", - "puts", - "ungetc", - "scanf", - "fscanf", - "sscanf", - "vscanf", - "vfscanf", - "vsscanf", - "printf", - "fprintf", - "sprintf", - "snprintf", - "vprintf", - "vfprintf", - "vsprintf", - "vsnprintf", - "ftell", - "fgetpos", - "fseek", - "fsetpos", - "clearerr", - "feof", - "ferror", - "perror", - "tmpfile", - "tmpnam", - ), - ), -) - -_re_pattern_headers_maybe_templates: list[tuple[re.Pattern, str, str]] = [] -for _header, _templates in _HEADERS_MAYBE_TEMPLATES: - # Match max(..., ...), max(..., ...), but not foo->max, foo.max or - # 'type::max()'. - _re_pattern_headers_maybe_templates.extend( - (re.compile(r"((\bstd::)|[^>.:])\b" + _template + r"(<.*?>)?\([^\)]"), _template, _header) - for _template in _templates - ) - -# Map is often overloaded. Only check, if it is fully qualified. -# Match 'std::map(...)', but not 'map(...)'' -_re_pattern_headers_maybe_templates.append( - (re.compile(r"(std\b::\bmap\s*\<)|(^(std\b::\b)map\b\(\s*\<)"), "map<>", "") -) - -# Other scripts may reach in and modify this pattern. -_re_pattern_templates: list[tuple[re.Pattern, str, str]] = [] -for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: - _re_pattern_templates.extend( - ( - re.compile(r"((^|(^|\s|((^|\W)::))std::)|[^>.:]\b)" + _template + r"\s*\<"), - _template + "<>", - _header, - ) - for _template in _templates - ) - -_re_pattern_types_or_objs: list[tuple[re.Pattern, object | type, str]] = [] -for _header, _types_or_objs in _HEADERS_TYPES_OR_OBJS: - _re_pattern_types_or_objs.extend( - (re.compile(r"\b" + _type_or_obj + r"\b"), _type_or_obj, _header) - for _type_or_obj in _types_or_objs - ) - -_re_pattern_functions: list[tuple[re.Pattern, str, str]] = [] -for _header, _functions in _HEADERS_FUNCTIONS: - # Match printf(..., ...), but not foo->printf, foo.printf or - # 'type::printf()'. - _re_pattern_functions.extend( - (re.compile(r"([^>.]|^)\b" + _function + r"\([^\)]"), _function, _header) - for _function in _functions - ) - - -def FilesBelongToSameModule(filename_cc, filename_h): - """Check if these two filenames belong to the same module. - - The concept of a 'module' here is a as follows: - foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the - same 'module' if they are in the same directory. - some/path/public/xyzzy and some/path/internal/xyzzy are also considered - to belong to the same module here. - - If the filename_cc contains a longer path than the filename_h, for example, - '/absolute/path/to/base/sysinfo.cc', and this file would include - 'base/sysinfo.h', this function also produces the prefix needed to open the - header. This is used by the caller of this function to more robustly open the - header file. We don't have access to the real include paths in this context, - so we need this guesswork here. - - Known bugs: tools/base/bar.cc and base/bar.h belong to the same module - according to this implementation. Because of this, this function gives - some false positives. This should be sufficiently rare in practice. - - Args: - filename_cc: is the path for the source (e.g. .cc) file - filename_h: is the path for the header path - - Returns: - Tuple with a bool and a string: - bool: True if filename_cc and filename_h belong to the same module. - string: the additional prefix needed to open the header file. - """ - fileinfo_cc = FileInfo(filename_cc) - if fileinfo_cc.Extension().lstrip(".") not in GetNonHeaderExtensions(): - return (False, "") - - fileinfo_h = FileInfo(filename_h) - if not IsHeaderExtension(fileinfo_h.Extension().lstrip(".")): - return (False, "") - - filename_cc = filename_cc[: -(len(fileinfo_cc.Extension()))] - if matched_test_suffix := re.search(_TEST_FILE_SUFFIX, fileinfo_cc.BaseName()): - filename_cc = filename_cc[: -len(matched_test_suffix.group(1))] - - filename_cc = filename_cc.replace("/public/", "/") - filename_cc = filename_cc.replace("/internal/", "/") - - filename_h = filename_h[: -(len(fileinfo_h.Extension()))] - filename_h = filename_h.removesuffix("-inl") - filename_h = filename_h.replace("/public/", "/") - filename_h = filename_h.replace("/internal/", "/") - - files_belong_to_same_module = filename_cc.endswith(filename_h) - common_path = "" - if files_belong_to_same_module: - common_path = filename_cc[: -len(filename_h)] - return files_belong_to_same_module, common_path - - -def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs): - """Reports for missing stl includes. - - This function will output warnings to make sure you are including the headers - necessary for the stl containers and functions that you use. We only give one - reason to include a header. For example, if you use both equal_to<> and - less<> in a .h file, only one (the latter in the file) of these will be - reported as a reason to include the . - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - include_state: An _IncludeState instance. - error: The function to call with any errors found. - io: The IO factory to use to read the header file. Provided for unittest - injection. - """ - required = {} # A map of header name to linenumber and the template entity. - # Example of required: { '': (1219, 'less<>') } - - for linenum in range(clean_lines.NumLines()): - line = clean_lines.elided[linenum] - if not line or line[0] == "#": - continue - - _re_patterns = [] - _re_patterns.extend(_re_pattern_types_or_objs) - _re_patterns.extend(_re_pattern_functions) - for pattern, item, header in _re_patterns: - matched = pattern.search(line) - if matched: - # Don't warn about strings in non-STL namespaces: - # (We check only the first match per line; good enough.) - prefix = line[: matched.start()] - if prefix.endswith("std::") or not prefix.endswith("::"): - required[header] = (linenum, item) - - for pattern, template, header in _re_pattern_headers_maybe_templates: - if pattern.search(line): - required[header] = (linenum, template) - - # The following function is just a speed up, no semantics are changed. - if "<" not in line: # Reduces the cpu time usage by skipping lines. - continue - - for pattern, template, header in _re_pattern_templates: - matched = pattern.search(line) - if matched: - # Don't warn about IWYU in non-STL namespaces: - # (We check only the first match per line; good enough.) - prefix = line[: matched.start()] - if prefix.endswith("std::") or not prefix.endswith("::"): - required[header] = (linenum, template) - - # Let's flatten the include_state include_list and copy it into a dictionary. - include_dict = dict([item for sublist in include_state.include_list for item in sublist]) - - # All the lines have been processed, report the errors found. - for header in sorted(required, key=required.__getitem__): - template = required[header][1] - header_stripped = header.strip('<>"') - if header_stripped not in include_dict and not ( - header_stripped[0] == "c" and (header_stripped[1:] + ".h") in include_dict - ): - error( - filename, - required[header][0], - "build/include_what_you_use", - 4, - "Add #include " + header + " for " + template, - ) - - -_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r"\bmake_pair\s*<") - - -def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): - """Check that make_pair's template arguments are deduced. - - G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are - specified explicitly, and such use isn't intended in any case. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) - if match: - error( - filename, - linenum, - "build/explicit_make_pair", - 4, # 4 = high confidence - "For C++11-compatibility, omit template arguments from make_pair" - " OR use pair directly OR if appropriate, construct a pair directly", - ) - - -def CheckRedundantVirtual(filename, clean_lines, linenum, error): - """Check if line contains a redundant "virtual" function-specifier. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Look for "virtual" on current line. - line = clean_lines.elided[linenum] - virtual = re.match(r"^(.*)(\bvirtual\b)(.*)$", line) - if not virtual: - return - - # Ignore "virtual" keywords that are near access-specifiers. These - # are only used in class base-specifier and do not apply to member - # functions. - if re.search(r"\b(public|protected|private)\s+$", virtual.group(1)) or re.match( - r"^\s+(public|protected|private)\b", virtual.group(3) - ): - return - - # Ignore the "virtual" keyword from virtual base classes. Usually - # there is a column on the same line in these cases (virtual base - # classes are rare in google3 because multiple inheritance is rare). - if re.match(r"^.*[^:]:[^:].*$", line): - return - - # Look for the next opening parenthesis. This is the start of the - # parameter list (possibly on the next line shortly after virtual). - # TODO(google): doesn't work if there are virtual functions with - # decltype() or other things that use parentheses, but csearch suggests - # that this is rare. - end_col = -1 - end_line = -1 - start_col = len(virtual.group(2)) - for start_line in range(linenum, min(linenum + 3, clean_lines.NumLines())): - line = clean_lines.elided[start_line][start_col:] - parameter_list = re.match(r"^([^(]*)\(", line) - if parameter_list: - # Match parentheses to find the end of the parameter list - (_, end_line, end_col) = CloseExpression( - clean_lines, start_line, start_col + len(parameter_list.group(1)) - ) - break - start_col = 0 - - if end_col < 0: - return # Couldn't find end of parameter list, give up - - # Look for "override" or "final" after the parameter list - # (possibly on the next few lines). - for i in range(end_line, min(end_line + 3, clean_lines.NumLines())): - line = clean_lines.elided[i][end_col:] - match = re.search(r"\b(override|final)\b", line) - if match: - error( - filename, - linenum, - "readability/inheritance", - 4, - ( - '"virtual" is redundant since function is ' - f'already declared as "{match.group(1)}"' - ), - ) - - # Set end_col to check whole lines after we are done with the - # first line. - end_col = 0 - if re.search(r"[^\w]\s*$", line): - break - - -def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): - """Check if line contains a redundant "override" or "final" virt-specifier. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Look for closing parenthesis nearby. We need one to confirm where - # the declarator ends and where the virt-specifier starts to avoid - # false positives. - line = clean_lines.elided[linenum] - if (declarator_end := line.rfind(")")) >= 0: - fragment = line[declarator_end:] - else: - if linenum > 1 and clean_lines.elided[linenum - 1].rfind(")") >= 0: - fragment = line - else: - return - - # Check that at most one of "override" or "final" is present, not both - if re.search(r"\boverride\b", fragment) and re.search(r"\bfinal\b", fragment): - error( - filename, - linenum, - "readability/inheritance", - 4, - ('"override" is redundant since function is already declared as "final"'), - ) - - -# Returns true if we are at a new block, and it is directly -# inside of a namespace. -def IsBlockInNameSpace(nesting_state: NestingState, is_forward_declaration: bool): # noqa: FBT001 - """Checks that the new block is directly in a namespace. - - Args: - nesting_state: The NestingState object that contains info about our state. - is_forward_declaration: If the class is a forward declared class. - Returns: - Whether or not the new block is directly in a namespace. - """ - if is_forward_declaration: - return len(nesting_state.stack) >= 1 and ( - isinstance(nesting_state.stack[-1], _NamespaceInfo) - ) - - if len(nesting_state.stack) >= 1: - if isinstance(nesting_state.stack[-1], _NamespaceInfo): - return True - if ( - len(nesting_state.stack) > 1 - and isinstance(nesting_state.previous_stack_top, _NamespaceInfo) - and ( - isinstance(nesting_state.stack[-2], _NamespaceInfo) - or len(nesting_state.stack) > 2 # Accommodate for WrappedInfo - and issubclass(type(nesting_state.stack[-1]), _WrappedInfo) - and not nesting_state.stack[-2].seen_open_brace - and isinstance(nesting_state.stack[-3], _NamespaceInfo) - ) - ): - return True - return False - - -def ShouldCheckNamespaceIndentation( - nesting_state: NestingState, is_namespace_indent_item, raw_lines_no_comments, linenum -): - """This method determines if we should apply our namespace indentation check. - - Args: - nesting_state: The current nesting state. - is_namespace_indent_item: If we just put a new class on the stack, True. - If the top of the stack is not a class, or we did not recently - add the class, False. - raw_lines_no_comments: The lines without the comments. - linenum: The current line number we are processing. - - Returns: - True if we should apply our namespace indentation check. Currently, it - only works for classes and namespaces inside of a namespace. - """ - - # Required by all checks involving nesting_state - if not nesting_state.stack: - return False - - is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, linenum) - - if not (is_namespace_indent_item or is_forward_declaration): - return False - - # If we are in a macro, we do not want to check the namespace indentation. - if IsMacroDefinition(raw_lines_no_comments, linenum): - return False - - # Skip if we are inside an open parenthesis block (e.g. function parameters). - if nesting_state.previous_stack_top and nesting_state.previous_open_parentheses > 0: - return False - - # Skip if we are extra-indenting a member initializer list. - if ( - isinstance(nesting_state.previous_stack_top, _ConstructorInfo) # F/N (A::A() : _a(0) {/{}) - and ( - isinstance(nesting_state.stack[-1], _MemInitListInfo) - or isinstance(nesting_state.popped_top, _MemInitListInfo) - ) - ) or ( # popping constructor after MemInitList on the same line (: _a(a) {}) - isinstance(nesting_state.previous_stack_top, _ConstructorInfo) - and isinstance(nesting_state.popped_top, _ConstructorInfo) - and re.search(r"[^:]:[^:]", raw_lines_no_comments[linenum]) - ): - return False - - return IsBlockInNameSpace(nesting_state, is_forward_declaration) - - -# Call this method if the line is directly inside of a namespace. -# If the line above is blank (excluding comments) or the start of -# an inner namespace, it cannot be indented. -def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, error): - line = raw_lines_no_comments[linenum] - if re.match(r"^\s+", line): - error( - filename, linenum, "whitespace/indent_namespace", 4, "Do not indent within a namespace." - ) - - -def ProcessLine( - filename, - file_extension, - clean_lines, - line, - include_state, - function_state, - nesting_state, - error, - extra_check_functions=None, - cppvar=None, -): - """Processes a single line in the file. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - clean_lines: An array of strings, each representing a line of the file, - with comments stripped. - line: Number of line being processed. - include_state: An _IncludeState instance in which the headers are inserted. - function_state: A _FunctionState instance which counts function lines, etc. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: A callable to which errors are reported, which takes 4 arguments: - filename, line number, error level, and message - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - cppvar: The header guard variable returned by GetHeaderGuardCPPVar. - """ - raw_lines = clean_lines.raw_lines - ParseNolintSuppressions(filename, raw_lines[line], line, error) - nesting_state.Update(filename, clean_lines, line, error) - CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, error) - if nesting_state.InAsmBlock(): - return - CheckForFunctionLengths(filename, clean_lines, line, function_state, error) - CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) - CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error, cppvar) - CheckLanguage(filename, clean_lines, line, file_extension, include_state, nesting_state, error) - CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) - CheckForNonStandardConstructs(filename, clean_lines, line, nesting_state, error) - CheckVlogArguments(filename, clean_lines, line, error) - CheckPosixThreading(filename, clean_lines, line, error) - CheckInvalidIncrement(filename, clean_lines, line, error) - CheckMakePairUsesDeduction(filename, clean_lines, line, error) - CheckRedundantVirtual(filename, clean_lines, line, error) - CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) - if extra_check_functions: - for check_fn in extra_check_functions: - check_fn(filename, clean_lines, line, error) - - -def FlagCxxHeaders(filename, clean_lines, linenum, error): - """Flag C++ headers that the styleguide restricts. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = re.match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++11 headers. - if include and include.group(1) in ( - "cfenv", - "fenv.h", - "ratio", - ): - error( - filename, - linenum, - "build/c++11", - 5, - f"<{include.group(1)}> is an unapproved C++11 header.", - ) - - # filesystem is the only unapproved C++17 header - if include and include.group(1) == "filesystem": - error(filename, linenum, "build/c++17", 5, " is an unapproved C++17 header.") - - -def ProcessFileData(filename, file_extension, lines, error, extra_check_functions=None): - """Performs lint checks and reports any errors to the given error function. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - lines: An array of strings, each representing a line of the file, with the - last element being empty if the file is terminated with a newline. - error: A callable to which errors are reported, which takes 4 arguments: - filename, line number, error level, and message - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - """ - lines = ( - ["// marker so line numbers and indices both start at 1"] - + lines - + ["// marker so line numbers end in a known way"] - ) - - include_state = _IncludeState() - function_state = _FunctionState() - nesting_state = NestingState() - - ResetNolintSuppressions() - - CheckForCopyright(filename, lines, error) - ProcessGlobalSuppressions(filename, lines) - RemoveMultiLineComments(filename, lines, error) - clean_lines = CleansedLines(lines) - - cppvar = None - if IsHeaderExtension(file_extension): - cppvar = GetHeaderGuardCPPVariable(filename) - CheckForHeaderGuard(filename, clean_lines, error, cppvar) - - for line in range(clean_lines.NumLines()): - ProcessLine( - filename, - file_extension, - clean_lines, - line, - include_state, - function_state, - nesting_state, - error, - extra_check_functions, - cppvar, - ) - FlagCxxHeaders(filename, clean_lines, line, error) - if _error_suppressions.HasOpenBlock(): - error( - filename, - _error_suppressions.GetOpenBlockStart(), - "readability/nolint", - 5, - "NONLINT block never ended", - ) - - CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) - - # Check that the .cc file has included its header if it exists. - if _IsSourceExtension(file_extension): - CheckHeaderFileIncluded(filename, include_state, error) - - # We check here rather than inside ProcessLine so that we see raw - # lines rather than "cleaned" lines. - CheckForBadCharacters(filename, lines, error) - - CheckForNewlineAtEOF(filename, lines, error) - - -def ProcessConfigOverrides(filename): - """Loads the configuration files and processes the config overrides. - - Args: - filename: The name of the file being processed by the linter. - - Returns: - False if the current |filename| should not be processed further. - """ - - abs_filename = os.path.abspath(filename) - cfg_filters = [] - keep_looking = True - while keep_looking: - abs_path, base_name = os.path.split(abs_filename) - if not base_name: - break # Reached the root directory. - - cfg_file = os.path.join(abs_path, _config_filename) - abs_filename = abs_path - if not os.path.isfile(cfg_file): - continue - - try: - with codecs.open(cfg_file, "r", "utf8", "replace") as file_handle: - for line in file_handle: - line, _, _ = line.partition("#") # Remove comments. - if not line.strip(): - continue - - name, _, val = line.partition("=") - name = name.strip() - val = val.strip() - if name == "set noparent": - keep_looking = False - elif name == "filter": - cfg_filters.append(val) - elif name == "exclude_files": - # When matching exclude_files pattern, use the base_name of - # the current file name or the directory name we are processing. - # For example, if we are checking for lint errors in /foo/bar/baz.cc - # and we found the .cfg file at /foo/CPPLINT.cfg, then the config - # file's "exclude_files" filter is meant to be checked against "bar" - # and not "baz" nor "bar/baz.cc". - if base_name: - pattern = re.compile(val) - if pattern.match(base_name): - if _cpplint_state.quiet: - # Suppress "Ignoring file" warning when using --quiet. - return False - _cpplint_state.PrintInfo( - f'Ignoring "{filename}": file excluded by "{cfg_file}". ' - 'File path component "%s" matches ' - 'pattern "%s"\n' % (base_name, val) - ) - return False - elif name == "linelength": - global _line_length - try: - _line_length = int(val) - except ValueError: - _cpplint_state.PrintError("Line length must be numeric.") - elif name == "extensions": - ProcessExtensionsOption(val) - elif name == "root": - global _root - # root directories are specified relative to CPPLINT.cfg dir. - _root = os.path.join(os.path.dirname(cfg_file), val) - elif name == "headers": - ProcessHppHeadersOption(val) - elif name == "includeorder": - ProcessIncludeOrderOption(val) - else: - _cpplint_state.PrintError( - f"Invalid configuration option ({name}) in file {cfg_file}\n" - ) - - except OSError: - _cpplint_state.PrintError( - f"Skipping config file '{cfg_file}': Can't open for reading\n" - ) - keep_looking = False - - # Apply all the accumulated filters in reverse order (top-level directory - # config options having the least priority). - for cfg_filter in reversed(cfg_filters): - _AddFilters(cfg_filter) - - return True - - -def ProcessFile(filename, vlevel, extra_check_functions=None): - """Does google-lint on a single file. - - Args: - filename: The name of the file to parse. - - vlevel: The level of errors to report. Every error of confidence - >= verbose_level will be reported. 0 is a good default. - - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - """ - - _SetVerboseLevel(vlevel) - _BackupFilters() - old_errors = _cpplint_state.error_count - - if not ProcessConfigOverrides(filename): - _RestoreFilters() - return - - lf_lines = [] - crlf_lines = [] - try: - # Support the UNIX convention of using "-" for stdin. Note that - # we are not opening the file with universal newline support - # (which codecs doesn't support anyway), so the resulting lines do - # contain trailing '\r' characters if we are reading a file that - # has CRLF endings. - # If after the split a trailing '\r' is present, it is removed - # below. - if filename == "-": - lines = sys.stdin.read().split("\n") - else: - with codecs.open(filename, "r", "utf8", "replace") as target_file: - lines = target_file.read().split("\n") - - # Remove trailing '\r'. - # The -1 accounts for the extra trailing blank line we get from split() - for linenum in range(len(lines) - 1): - if lines[linenum].endswith("\r"): - lines[linenum] = lines[linenum].rstrip("\r") - crlf_lines.append(linenum + 1) - else: - lf_lines.append(linenum + 1) - - except OSError: - # TODO(aaronliu0130): Maybe make this have an exit code of 2 after all is done - _cpplint_state.PrintError(f"Skipping input '{filename}': Can't open for reading\n") - _RestoreFilters() - return - - # Note, if no dot is found, this will give the entire filename as the ext. - file_extension = filename[filename.rfind(".") + 1 :] - - # When reading from stdin, the extension is unknown, so no cpplint tests - # should rely on the extension. - if filename != "-" and file_extension not in GetAllExtensions(): - _cpplint_state.PrintError( - f"Ignoring {filename}; not a valid file name ({(', '.join(GetAllExtensions()))})\n" - ) - else: - ProcessFileData(filename, file_extension, lines, Error, extra_check_functions) - - # If end-of-line sequences are a mix of LF and CR-LF, issue - # warnings on the lines with CR. - # - # Don't issue any warnings if all lines are uniformly LF or CR-LF, - # since critique can handle these just fine, and the style guide - # doesn't dictate a particular end of line sequence. - # - # We can't depend on os.linesep to determine what the desired - # end-of-line sequence should be, since that will return the - # server-side end-of-line sequence. - if lf_lines and crlf_lines: - # Warn on every line with CR. An alternative approach might be to - # check whether the file is mostly CRLF or just LF, and warn on the - # minority, we bias toward LF here since most tools prefer LF. - for linenum in crlf_lines: - Error( - filename, - linenum, - "whitespace/newline", - 1, - "Unexpected \\r (^M) found; better to use only \\n", - ) - - # Suppress printing anything if --quiet was passed unless the error - # count has increased after processing this file. - if not _cpplint_state.quiet or old_errors != _cpplint_state.error_count: - _cpplint_state.PrintInfo(f"Done processing {filename}\n") - _RestoreFilters() - - -def PrintUsage(message): - """Prints a brief usage string and exits, optionally with an error message. - - Args: - message: The optional error message. - """ - sys.stderr.write( - _USAGE - % ( - sorted(GetAllExtensions()), - ",".join(sorted(GetAllExtensions())), - sorted(GetHeaderExtensions()), - ",".join(sorted(GetHeaderExtensions())), - ) - ) - - if message: - sys.exit("\nFATAL ERROR: " + message) - else: - sys.exit(0) - - -def PrintVersion(): - sys.stdout.write("Cpplint fork (https://github.com/cpplint/cpplint)\n") - sys.stdout.write("cpplint " + __VERSION__ + "\n") - sys.stdout.write("Python " + sys.version + "\n") - sys.exit(0) - - -def PrintCategories(): - """Prints a list of all the error-categories used by error messages. - - These are the categories used to filter messages via --filter. - """ - sys.stderr.write("".join(f" {cat}\n" for cat in _ERROR_CATEGORIES)) - sys.exit(0) - - -def ParseArguments(args): - """Parses the command line arguments. - - This may set the output format and verbosity level as side-effects. - - Args: - args: The command line arguments: - - Returns: - The list of filenames to lint. - """ - try: - (opts, filenames) = getopt.getopt( - args, - "", - [ - "help", - "output=", - "verbose=", - "v=", - "version", - "counting=", - "filter=", - "root=", - "repository=", - "linelength=", - "extensions=", - "exclude=", - "recursive", - "headers=", - "includeorder=", - "config=", - "quiet", - ], - ) - except getopt.GetoptError: - PrintUsage("Invalid arguments.") - - verbosity = _VerboseLevel() - output_format = _OutputFormat() - filters = "" - quiet = _Quiet() - counting_style = "" - recursive = False - - for opt, val in opts: - if opt == "--help": - PrintUsage(None) - if opt == "--version": - PrintVersion() - elif opt == "--output": - if val not in ("emacs", "vs7", "eclipse", "junit", "sed", "gsed"): - PrintUsage( - "The only allowed output formats are emacs, vs7, eclipse sed, gsed and junit." - ) - output_format = val - elif opt == "--quiet": - quiet = True - elif opt in {"--verbose", "--v"}: - verbosity = int(val) - elif opt == "--filter": - filters = val - if not filters: - PrintCategories() - elif opt == "--counting": - if val not in ("total", "toplevel", "detailed"): - PrintUsage("Valid counting options are total, toplevel, and detailed") - counting_style = val - elif opt == "--root": - global _root - _root = val - elif opt == "--repository": - global _repository - _repository = val - elif opt == "--linelength": - global _line_length - try: - _line_length = int(val) - except ValueError: - PrintUsage("Line length must be digits.") - elif opt == "--exclude": - global _excludes - if not _excludes: - _excludes = set() - _excludes.update(glob.glob(val)) - elif opt == "--extensions": - ProcessExtensionsOption(val) - elif opt == "--headers": - ProcessHppHeadersOption(val) - elif opt == "--recursive": - recursive = True - elif opt == "--includeorder": - ProcessIncludeOrderOption(val) - elif opt == "--config": - global _config_filename - _config_filename = val - if os.path.basename(_config_filename) != _config_filename: - PrintUsage("Config file name must not include directory components.") - - if not filenames: - PrintUsage("No files were specified.") - - if recursive: - filenames = _ExpandDirectories(filenames) - - if _excludes: - filenames = _FilterExcludedFiles(filenames) - - _SetOutputFormat(output_format) - _SetQuiet(quiet) - _SetVerboseLevel(verbosity) - _SetFilters(filters) - _SetCountingStyle(counting_style) - - filenames.sort() - return filenames - - -def _ParseFilterSelector(parameter): - """Parses the given command line parameter for file- and line-specific - exclusions. - readability/casting:file.cpp - readability/casting:file.cpp:43 - - Args: - parameter: The parameter value of --filter - - Returns: - [category, filename, line]. - Category is always given. - Filename is either a filename or empty if all files are meant. - Line is either a line in filename or -1 if all lines are meant. - """ - colon_pos = parameter.find(":") - if colon_pos == -1: - return parameter, "", -1 - category = parameter[:colon_pos] - second_colon_pos = parameter.find(":", colon_pos + 1) - if second_colon_pos == -1: - return category, parameter[colon_pos + 1 :], -1 - return ( - category, - parameter[colon_pos + 1 : second_colon_pos], - int(parameter[second_colon_pos + 1 :]), - ) - - -def _ExpandDirectories(filenames): - """Searches a list of filenames and replaces directories in the list with - all files descending from those directories. Files with extensions not in - the valid extensions list are excluded. - - Args: - filenames: A list of files or directories - - Returns: - A list of all files that are members of filenames or descended from a - directory in filenames - """ - expanded = set() - for filename in filenames: - if not os.path.isdir(filename): - expanded.add(filename) - continue - - for root, _, files in os.walk(filename): - for loopfile in files: - fullname = os.path.join(root, loopfile) - fullname = fullname.removeprefix("." + os.path.sep) - expanded.add(fullname) - - return [ - filename for filename in expanded if os.path.splitext(filename)[1][1:] in GetAllExtensions() - ] - - -def _FilterExcludedFiles(fnames): - """Filters out files listed in the --exclude command line switch. File paths - in the switch are evaluated relative to the current working directory - """ - exclude_paths = [os.path.abspath(f) for f in _excludes] - # because globbing does not work recursively, exclude all subpath of all excluded entries - return [ - f - for f in fnames - if not any(e for e in exclude_paths if _IsParentOrSame(e, os.path.abspath(f))) - ] - - -def _IsParentOrSame(parent, child): - """Return true if child is subdirectory of parent. - Assumes both paths are absolute and don't contain symlinks. - """ - parent = os.path.normpath(parent) - child = os.path.normpath(child) - if parent == child: - return True - - prefix = os.path.commonprefix([parent, child]) - if prefix != parent: - return False - # Note: os.path.commonprefix operates on character basis, so - # take extra care of situations like '/foo/ba' and '/foo/bar/baz' - child_suffix = child[len(prefix) :] - child_suffix = child_suffix.lstrip(os.sep) - return child == os.path.join(prefix, child_suffix) - - -def main(): - filenames = ParseArguments(sys.argv[1:]) - backup_err = sys.stderr - try: - # Change stderr to write with replacement characters so we don't die - # if we try to print something containing non-ASCII characters. - sys.stderr = codecs.StreamReader(sys.stderr, "replace") - - _cpplint_state.ResetErrorCounts() - for filename in filenames: - ProcessFile(filename, _cpplint_state.verbose_level) - # If --quiet is passed, suppress printing error count unless there are errors. - if not _cpplint_state.quiet or _cpplint_state.error_count > 0: - _cpplint_state.PrintErrorCounts() - - if _cpplint_state.output_format == "junit": - sys.stderr.write(_cpplint_state.FormatJUnitXML()) - - finally: - sys.stderr = backup_err - - sys.exit(_cpplint_state.error_count > 0) - - -if __name__ == "__main__": - main() diff --git a/csslint.js b/csslint.js deleted file mode 100644 index 7621079d97..0000000000 --- a/csslint.js +++ /dev/null @@ -1,11283 +0,0 @@ -/*jslint-disable*/ -/* -shRollupFetch -{ - "fetchList": [ - { - "comment": true, - "url": "https://github.com/CSSLint/csslint/blob/e8aeeda06c928636e21428e09b1af93f66621209/LICENSE" - }, - { - "url": "https://github.com/CSSLint/csslint/blob/e8aeeda06c928636e21428e09b1af93f66621209/dist/csslint.js" - }, - { - "url": "https://github.com/CSSLint/csslint/blob/e8aeeda06c928636e21428e09b1af93f66621209/dist/cli.js" - } - ], - "replaceList": [] -} -*/ - - -/* -repo https://github.com/CSSLint/csslint/tree/e8aeeda06c928636e21428e09b1af93f66621209 -committed 2018-02-25T11:28:16Z -*/ - - -/* -file https://github.com/CSSLint/csslint/blob/e8aeeda06c928636e21428e09b1af93f66621209/LICENSE -*/ -/* -CSSLint -Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - - -/* -file https://github.com/CSSLint/csslint/blob/e8aeeda06c928636e21428e09b1af93f66621209/dist/csslint.js -*/ -/*! -CSSLint v1.0.5 -Copyright (c) 2017 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -var CSSLint = (function(){ - var module = module || {}, - exports = exports || {}; - -/*! -Parser-Lib -Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -/* Version v1.1.0, Build time: 6-December-2016 10:31:29 */ -var parserlib = (function () { -var require; -require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o). - * @namespace parserlib.css - * @class Combinator - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} text The text representation of the unit. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function Combinator(text, line, col) { - - SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); - - /** - * The type of modifier. - * @type String - * @property type - */ - this.type = "unknown"; - - //pretty simple - if (/^\s+$/.test(text)) { - this.type = "descendant"; - } else if (text === ">") { - this.type = "child"; - } else if (text === "+") { - this.type = "adjacent-sibling"; - } else if (text === "~") { - this.type = "sibling"; - } -} - -Combinator.prototype = new SyntaxUnit(); -Combinator.prototype.constructor = Combinator; -},{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){ -"use strict"; - -module.exports = Matcher; - -var StringReader = require("../util/StringReader"); -var SyntaxError = require("../util/SyntaxError"); - -/** - * This class implements a combinator library for matcher functions. - * The combinators are described at: - * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators - */ -function Matcher(matchFunc, toString) { - this.match = function(expression) { - // Save/restore marks to ensure that failed matches always restore - // the original location in the expression. - var result; - expression.mark(); - result = matchFunc(expression); - if (result) { - expression.drop(); - } else { - expression.restore(); - } - return result; - }; - this.toString = typeof toString === "function" ? toString : function() { - return toString; - }; -} - -/** Precedence table of combinators. */ -Matcher.prec = { - MOD: 5, - SEQ: 4, - ANDAND: 3, - OROR: 2, - ALT: 1 -}; - -/** Simple recursive-descent grammar to build matchers from strings. */ -Matcher.parse = function(str) { - var reader, eat, expr, oror, andand, seq, mod, term, result; - reader = new StringReader(str); - eat = function(matcher) { - var result = reader.readMatch(matcher); - if (result === null) { - throw new SyntaxError( - "Expected "+matcher, reader.getLine(), reader.getCol()); - } - return result; - }; - expr = function() { - // expr = oror (" | " oror)* - var m = [ oror() ]; - while (reader.readMatch(" | ") !== null) { - m.push(oror()); - } - return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m); - }; - oror = function() { - // oror = andand ( " || " andand)* - var m = [ andand() ]; - while (reader.readMatch(" || ") !== null) { - m.push(andand()); - } - return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m); - }; - andand = function() { - // andand = seq ( " && " seq)* - var m = [ seq() ]; - while (reader.readMatch(" && ") !== null) { - m.push(seq()); - } - return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); - }; - seq = function() { - // seq = mod ( " " mod)* - var m = [ mod() ]; - while (reader.readMatch(/^ (?![&|\]])/) !== null) { - m.push(mod()); - } - return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m); - }; - mod = function() { - // mod = term ( "?" | "*" | "+" | "#" | "{,}" )? - var m = term(); - if (reader.readMatch("?") !== null) { - return m.question(); - } else if (reader.readMatch("*") !== null) { - return m.star(); - } else if (reader.readMatch("+") !== null) { - return m.plus(); - } else if (reader.readMatch("#") !== null) { - return m.hash(); - } else if (reader.readMatch(/^\{\s*/) !== null) { - var min = eat(/^\d+/); - eat(/^\s*,\s*/); - var max = eat(/^\d+/); - eat(/^\s*\}/); - return m.braces(+min, +max); - } - return m; - }; - term = function() { - // term = | literal | "[ " expression " ]" - if (reader.readMatch("[ ") !== null) { - var m = expr(); - eat(" ]"); - return m; - } - return Matcher.fromType(eat(/^[^ ?*+#{]+/)); - }; - result = expr(); - if (!reader.eof()) { - throw new SyntaxError( - "Expected end of string", reader.getLine(), reader.getCol()); - } - return result; -}; - -/** - * Convert a string to a matcher (parsing simple alternations), - * or do nothing if the argument is already a matcher. - */ -Matcher.cast = function(m) { - if (m instanceof Matcher) { - return m; - } - return Matcher.parse(m); -}; - -/** - * Create a matcher for a single type. - */ -Matcher.fromType = function(type) { - // Late require of ValidationTypes to break a dependency cycle. - var ValidationTypes = require("./ValidationTypes"); - return new Matcher(function(expression) { - return expression.hasNext() && ValidationTypes.isType(expression, type); - }, type); -}; - -/** - * Create a matcher for one or more juxtaposed words, which all must - * occur, in the given order. - */ -Matcher.seq = function() { - var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); - if (ms.length === 1) { - return ms[0]; - } - return new Matcher(function(expression) { - var i, result = true; - for (i = 0; result && i < ms.length; i++) { - result = ms[i].match(expression); - } - return result; - }, function(prec) { - var p = Matcher.prec.SEQ; - var s = ms.map(function(m) { - return m.toString(p); - }).join(" "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); -}; - -/** - * Create a matcher for one or more alternatives, where exactly one - * must occur. - */ -Matcher.alt = function() { - var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); - if (ms.length === 1) { - return ms[0]; - } - return new Matcher(function(expression) { - var i, result = false; - for (i = 0; !result && i < ms.length; i++) { - result = ms[i].match(expression); - } - return result; - }, function(prec) { - var p = Matcher.prec.ALT; - var s = ms.map(function(m) { - return m.toString(p); - }).join(" | "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); -}; - -/** - * Create a matcher for two or more options. This implements the - * double bar (||) and double ampersand (&&) operators, as well as - * variants of && where some of the alternatives are optional. - * This will backtrack through even successful matches to try to - * maximize the number of items matched. - */ -Matcher.many = function(required) { - var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { - if (v.expand) { - // Insert all of the options for the given complex rule as - // individual options. - var ValidationTypes = require("./ValidationTypes"); - acc.push.apply(acc, ValidationTypes.complex[v.expand].options); - } else { - acc.push(Matcher.cast(v)); - } - return acc; - }, []); - - if (required === true) { - required = ms.map(function() { - return true; - }); - } - - var result = new Matcher(function(expression) { - var seen = [], max = 0, pass = 0; - var success = function(matchCount) { - if (pass === 0) { - max = Math.max(matchCount, max); - return matchCount === ms.length; - } else { - return matchCount === max; - } - }; - var tryMatch = function(matchCount) { - for (var i = 0; i < ms.length; i++) { - if (seen[i]) { - continue; - } - expression.mark(); - if (ms[i].match(expression)) { - seen[i] = true; - // Increase matchCount iff this was a required element - // (or if all the elements are optional) - if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { - expression.drop(); - return true; - } - // Backtrack: try *not* matching using this rule, and - // let's see if it leads to a better overall match. - expression.restore(); - seen[i] = false; - } else { - expression.drop(); - } - } - return success(matchCount); - }; - if (!tryMatch(0)) { - // Couldn't get a complete match, retrace our steps to make the - // match with the maximum # of required elements. - pass++; - tryMatch(0); - } - - if (required === false) { - return max > 0; - } - // Use finer-grained specification of which matchers are required. - for (var i = 0; i < ms.length; i++) { - if (required[i] && !seen[i]) { - return false; - } - } - return true; - }, function(prec) { - var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; - var s = ms.map(function(m, i) { - if (required !== false && !required[i]) { - return m.toString(Matcher.prec.MOD) + "?"; - } - return m.toString(p); - }).join(required === false ? " || " : " && "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); - result.options = ms; - return result; -}; - -/** - * Create a matcher for two or more options, where all options are - * mandatory but they may appear in any order. - */ -Matcher.andand = function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(true); - return Matcher.many.apply(Matcher, args); -}; - -/** - * Create a matcher for two or more options, where options are - * optional and may appear in any order, but at least one must be - * present. - */ -Matcher.oror = function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(false); - return Matcher.many.apply(Matcher, args); -}; - -/** Instance methods on Matchers. */ -Matcher.prototype = { - constructor: Matcher, - // These are expected to be overridden in every instance. - match: function() { throw new Error("unimplemented"); }, - toString: function() { throw new Error("unimplemented"); }, - // This returns a standalone function to do the matching. - func: function() { return this.match.bind(this); }, - // Basic combinators - then: function(m) { return Matcher.seq(this, m); }, - or: function(m) { return Matcher.alt(this, m); }, - andand: function(m) { return Matcher.many(true, this, m); }, - oror: function(m) { return Matcher.many(false, this, m); }, - // Component value multipliers - star: function() { return this.braces(0, Infinity, "*"); }, - plus: function() { return this.braces(1, Infinity, "+"); }, - question: function() { return this.braces(0, 1, "?"); }, - hash: function() { - return this.braces(1, Infinity, "#", Matcher.cast(",")); - }, - braces: function(min, max, marker, optSep) { - var m1 = this, m2 = optSep ? optSep.then(this) : this; - if (!marker) { - marker = "{" + min + "," + max + "}"; - } - return new Matcher(function(expression) { - var result = true, i; - for (i = 0; i < max; i++) { - if (i > 0 && optSep) { - result = m2.match(expression); - } else { - result = m1.match(expression); - } - if (!result) { - break; - } - } - return i >= min; - }, function() { - return m1.toString(Matcher.prec.MOD) + marker; - }); - } -}; -},{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){ -"use strict"; - -module.exports = MediaFeature; - -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Parser = require("./Parser"); - -/** - * Represents a media feature, such as max-width:500. - * @namespace parserlib.css - * @class MediaFeature - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {SyntaxUnit} name The name of the feature. - * @param {SyntaxUnit} value The value of the feature or null if none. - */ -function MediaFeature(name, value) { - - SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); - - /** - * The name of the media feature - * @type String - * @property name - */ - this.name = name; - - /** - * The value for the feature or null if there is none. - * @type SyntaxUnit - * @property value - */ - this.value = value; -} - -MediaFeature.prototype = new SyntaxUnit(); -MediaFeature.prototype.constructor = MediaFeature; -},{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){ -"use strict"; - -module.exports = MediaQuery; - -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Parser = require("./Parser"); - -/** - * Represents an individual media query. - * @namespace parserlib.css - * @class MediaQuery - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} modifier The modifier "not" or "only" (or null). - * @param {String} mediaType The type of media (i.e., "print"). - * @param {Array} parts Array of selectors parts making up this selector. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function MediaQuery(modifier, mediaType, features, line, col) { - - SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); - - /** - * The media modifier ("not" or "only") - * @type String - * @property modifier - */ - this.modifier = modifier; - - /** - * The mediaType (i.e., "print") - * @type String - * @property mediaType - */ - this.mediaType = mediaType; - - /** - * The parts that make up the selector. - * @type Array - * @property features - */ - this.features = features; -} - -MediaQuery.prototype = new SyntaxUnit(); -MediaQuery.prototype.constructor = MediaQuery; -},{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){ -"use strict"; - -module.exports = Parser; - -var EventTarget = require("../util/EventTarget"); -var SyntaxError = require("../util/SyntaxError"); -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Combinator = require("./Combinator"); -var MediaFeature = require("./MediaFeature"); -var MediaQuery = require("./MediaQuery"); -var PropertyName = require("./PropertyName"); -var PropertyValue = require("./PropertyValue"); -var PropertyValuePart = require("./PropertyValuePart"); -var Selector = require("./Selector"); -var SelectorPart = require("./SelectorPart"); -var SelectorSubPart = require("./SelectorSubPart"); -var TokenStream = require("./TokenStream"); -var Tokens = require("./Tokens"); -var Validation = require("./Validation"); - -/** - * A CSS3 parser. - * @namespace parserlib.css - * @class Parser - * @constructor - * @param {Object} options (Optional) Various options for the parser: - * starHack (true|false) to allow IE6 star hack as valid, - * underscoreHack (true|false) to interpret leading underscores - * as IE6-7 targeting for known properties, ieFilters (true|false) - * to indicate that IE < 8 filters should be accepted and not throw - * syntax errors. - */ -function Parser(options) { - - //inherit event functionality - EventTarget.call(this); - - - this.options = options || {}; - - this._tokenStream = null; -} - -//Static constants -Parser.DEFAULT_TYPE = 0; -Parser.COMBINATOR_TYPE = 1; -Parser.MEDIA_FEATURE_TYPE = 2; -Parser.MEDIA_QUERY_TYPE = 3; -Parser.PROPERTY_NAME_TYPE = 4; -Parser.PROPERTY_VALUE_TYPE = 5; -Parser.PROPERTY_VALUE_PART_TYPE = 6; -Parser.SELECTOR_TYPE = 7; -Parser.SELECTOR_PART_TYPE = 8; -Parser.SELECTOR_SUB_PART_TYPE = 9; - -Parser.prototype = function() { - - var proto = new EventTarget(), //new prototype - prop, - additions = { - __proto__: null, - - //restore constructor - constructor: Parser, - - //instance constants - yuck - DEFAULT_TYPE : 0, - COMBINATOR_TYPE : 1, - MEDIA_FEATURE_TYPE : 2, - MEDIA_QUERY_TYPE : 3, - PROPERTY_NAME_TYPE : 4, - PROPERTY_VALUE_TYPE : 5, - PROPERTY_VALUE_PART_TYPE : 6, - SELECTOR_TYPE : 7, - SELECTOR_PART_TYPE : 8, - SELECTOR_SUB_PART_TYPE : 9, - - //----------------------------------------------------------------- - // Grammar - //----------------------------------------------------------------- - - _stylesheet: function() { - - /* - * stylesheet - * : [ CHARSET_SYM S* STRING S* ';' ]? - * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* - * [ namespace [S|CDO|CDC]* ]* - * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]* - * ; - */ - - var tokenStream = this._tokenStream, - count, - token, - tt; - - this.fire("startstylesheet"); - - //try to read character set - this._charset(); - - this._skipCruft(); - - //try to read imports - may be more than one - while (tokenStream.peek() === Tokens.IMPORT_SYM) { - this._import(); - this._skipCruft(); - } - - //try to read namespaces - may be more than one - while (tokenStream.peek() === Tokens.NAMESPACE_SYM) { - this._namespace(); - this._skipCruft(); - } - - //get the next token - tt = tokenStream.peek(); - - //try to read the rest - while (tt > Tokens.EOF) { - - try { - - switch (tt) { - case Tokens.MEDIA_SYM: - this._media(); - this._skipCruft(); - break; - case Tokens.PAGE_SYM: - this._page(); - this._skipCruft(); - break; - case Tokens.FONT_FACE_SYM: - this._font_face(); - this._skipCruft(); - break; - case Tokens.KEYFRAMES_SYM: - this._keyframes(); - this._skipCruft(); - break; - case Tokens.VIEWPORT_SYM: - this._viewport(); - this._skipCruft(); - break; - case Tokens.DOCUMENT_SYM: - this._document(); - this._skipCruft(); - break; - case Tokens.SUPPORTS_SYM: - this._supports(); - this._skipCruft(); - break; - case Tokens.UNKNOWN_SYM: //unknown @ rule - tokenStream.get(); - if (!this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: null, - message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", - line: tokenStream.LT(0).startLine, - col: tokenStream.LT(0).startCol - }); - - //skip braces - count=0; - while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) { - count++; //keep track of nesting depth - } - - while (count) { - tokenStream.advance([Tokens.RBRACE]); - count--; - } - } else { - //not a syntax error, rethrow it - throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); - } - break; - case Tokens.S: - this._readWhitespace(); - break; - default: - if (!this._ruleset()) { - - //error handling for known issues - switch (tt) { - case Tokens.CHARSET_SYM: - token = tokenStream.LT(1); - this._charset(false); - throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); - case Tokens.IMPORT_SYM: - token = tokenStream.LT(1); - this._import(false); - throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); - case Tokens.NAMESPACE_SYM: - token = tokenStream.LT(1); - this._namespace(false); - throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); - default: - tokenStream.get(); //get the last token - this._unexpectedToken(tokenStream.token()); - } - } - } - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - } else { - throw ex; - } - } - - tt = tokenStream.peek(); - } - - if (tt !== Tokens.EOF) { - this._unexpectedToken(tokenStream.token()); - } - - this.fire("endstylesheet"); - }, - - _charset: function(emit) { - var tokenStream = this._tokenStream, - charset, - token, - line, - col; - - if (tokenStream.match(Tokens.CHARSET_SYM)) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.STRING); - - token = tokenStream.token(); - charset = token.value; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.SEMICOLON); - - if (emit !== false) { - this.fire({ - type: "charset", - charset:charset, - line: line, - col: col - }); - } - } - }, - - _import: function(emit) { - /* - * import - * : IMPORT_SYM S* - * [STRING|URI] S* media_query_list? ';' S* - */ - - var tokenStream = this._tokenStream, - uri, - importToken, - mediaList = []; - - //read import symbol - tokenStream.mustMatch(Tokens.IMPORT_SYM); - importToken = tokenStream.token(); - this._readWhitespace(); - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - - //grab the URI value - uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1"); - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "import", - uri: uri, - media: mediaList, - line: importToken.startLine, - col: importToken.startCol - }); - } - }, - - _namespace: function(emit) { - /* - * namespace - * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* - */ - - var tokenStream = this._tokenStream, - line, - col, - prefix, - uri; - - //read import symbol - tokenStream.mustMatch(Tokens.NAMESPACE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - this._readWhitespace(); - - //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT - if (tokenStream.match(Tokens.IDENT)) { - prefix = tokenStream.token().value; - this._readWhitespace(); - } - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - /*if (!tokenStream.match(Tokens.STRING)){ - tokenStream.mustMatch(Tokens.URI); - }*/ - - //grab the URI value - uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); - - this._readWhitespace(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "namespace", - prefix: prefix, - uri: uri, - line: line, - col: col - }); - } - }, - - _supports: function(emit) { - /* - * supports_rule - * : SUPPORTS_SYM S* supports_condition S* group_rule_body - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - if (tokenStream.match(Tokens.SUPPORTS_SYM)) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - this._supports_condition(); - this._readWhitespace(); - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "startsupports", - line: line, - col: col - }); - } - - while (true) { - if (!this._ruleset()) { - break; - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - this.fire({ - type: "endsupports", - line: line, - col: col - }); - } - }, - - _supports_condition: function() { - /* - * supports_condition - * : supports_negation | supports_conjunction | supports_disjunction | - * supports_condition_in_parens - * ; - */ - var tokenStream = this._tokenStream, - ident; - - if (tokenStream.match(Tokens.IDENT)) { - ident = tokenStream.token().value.toLowerCase(); - - if (ident === "not") { - tokenStream.mustMatch(Tokens.S); - this._supports_condition_in_parens(); - } else { - tokenStream.unget(); - } - } else { - this._supports_condition_in_parens(); - this._readWhitespace(); - - while (tokenStream.peek() === Tokens.IDENT) { - ident = tokenStream.LT(1).value.toLowerCase(); - if (ident === "and" || ident === "or") { - tokenStream.mustMatch(Tokens.IDENT); - this._readWhitespace(); - this._supports_condition_in_parens(); - this._readWhitespace(); - } - } - } - }, - - _supports_condition_in_parens: function() { - /* - * supports_condition_in_parens - * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | - * general_enclosed - * ; - */ - var tokenStream = this._tokenStream, - ident; - - if (tokenStream.match(Tokens.LPAREN)) { - this._readWhitespace(); - if (tokenStream.match(Tokens.IDENT)) { - // look ahead for not keyword, if not given, continue with declaration condition. - ident = tokenStream.token().value.toLowerCase(); - if (ident === "not") { - this._readWhitespace(); - this._supports_condition(); - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RPAREN); - } else { - tokenStream.unget(); - this._supports_declaration_condition(false); - } - } else { - this._supports_condition(); - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RPAREN); - } - } else { - this._supports_declaration_condition(); - } - }, - - _supports_declaration_condition: function(requireStartParen) { - /* - * supports_declaration_condition - * : '(' S* declaration ')' - * ; - */ - var tokenStream = this._tokenStream; - - if (requireStartParen !== false) { - tokenStream.mustMatch(Tokens.LPAREN); - } - this._readWhitespace(); - this._declaration(); - tokenStream.mustMatch(Tokens.RPAREN); - }, - - _media: function() { - /* - * media - * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - mediaList;// = []; - - //look for @media - tokenStream.mustMatch(Tokens.MEDIA_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - this.fire({ - type: "startmedia", - media: mediaList, - line: line, - col: col - }); - - while (true) { - if (tokenStream.peek() === Tokens.PAGE_SYM) { - this._page(); - } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) { - this._font_face(); - } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) { - this._viewport(); - } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) { - this._document(); - } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) { - this._supports(); - } else if (tokenStream.peek() === Tokens.MEDIA_SYM) { - this._media(); - } else if (!this._ruleset()) { - break; - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - this.fire({ - type: "endmedia", - media: mediaList, - line: line, - col: col - }); - }, - - - //CSS3 Media Queries - _media_query_list: function() { - /* - * media_query_list - * : S* [media_query [ ',' S* media_query ]* ]? - * ; - */ - var tokenStream = this._tokenStream, - mediaList = []; - - - this._readWhitespace(); - - if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) { - mediaList.push(this._media_query()); - } - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - mediaList.push(this._media_query()); - } - - return mediaList; - }, - - /* - * Note: "expression" in the grammar maps to the _media_expression - * method. - - */ - _media_query: function() { - /* - * media_query - * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* - * | expression [ AND S* expression ]* - * ; - */ - var tokenStream = this._tokenStream, - type = null, - ident = null, - token = null, - expressions = []; - - if (tokenStream.match(Tokens.IDENT)) { - ident = tokenStream.token().value.toLowerCase(); - - //since there's no custom tokens for these, need to manually check - if (ident !== "only" && ident !== "not") { - tokenStream.unget(); - ident = null; - } else { - token = tokenStream.token(); - } - } - - this._readWhitespace(); - - if (tokenStream.peek() === Tokens.IDENT) { - type = this._media_type(); - if (token === null) { - token = tokenStream.token(); - } - } else if (tokenStream.peek() === Tokens.LPAREN) { - if (token === null) { - token = tokenStream.LT(1); - } - expressions.push(this._media_expression()); - } - - if (type === null && expressions.length === 0) { - return null; - } else { - this._readWhitespace(); - while (tokenStream.match(Tokens.IDENT)) { - if (tokenStream.token().value.toLowerCase() !== "and") { - this._unexpectedToken(tokenStream.token()); - } - - this._readWhitespace(); - expressions.push(this._media_expression()); - } - } - - return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); - }, - - //CSS3 Media Queries - _media_type: function() { - /* - * media_type - * : IDENT - * ; - */ - return this._media_feature(); - }, - - /** - * Note: in CSS3 Media Queries, this is called "expression". - * Renamed here to avoid conflict with CSS3 Selectors - * definition of "expression". Also note that "expr" in the - * grammar now maps to "expression" from CSS3 selectors. - * @method _media_expression - * @private - */ - _media_expression: function() { - /* - * expression - * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* - * ; - */ - var tokenStream = this._tokenStream, - feature = null, - token, - expression = null; - - tokenStream.mustMatch(Tokens.LPAREN); - - feature = this._media_feature(); - this._readWhitespace(); - - if (tokenStream.match(Tokens.COLON)) { - this._readWhitespace(); - token = tokenStream.LT(1); - expression = this._expression(); - } - - tokenStream.mustMatch(Tokens.RPAREN); - this._readWhitespace(); - - return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null); - }, - - //CSS3 Media Queries - _media_feature: function() { - /* - * media_feature - * : IDENT - * ; - */ - var tokenStream = this._tokenStream; - - this._readWhitespace(); - - tokenStream.mustMatch(Tokens.IDENT); - - return SyntaxUnit.fromToken(tokenStream.token()); - }, - - //CSS3 Paged Media - _page: function() { - /* - * page: - * PAGE_SYM S* IDENT? pseudo_page? S* - * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - identifier = null, - pseudoPage = null; - - //look for @page - tokenStream.mustMatch(Tokens.PAGE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - if (tokenStream.match(Tokens.IDENT)) { - identifier = tokenStream.token().value; - - //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. - if (identifier.toLowerCase() === "auto") { - this._unexpectedToken(tokenStream.token()); - } - } - - //see if there's a colon upcoming - if (tokenStream.peek() === Tokens.COLON) { - pseudoPage = this._pseudo_page(); - } - - this._readWhitespace(); - - this.fire({ - type: "startpage", - id: identifier, - pseudo: pseudoPage, - line: line, - col: col - }); - - this._readDeclarations(true, true); - - this.fire({ - type: "endpage", - id: identifier, - pseudo: pseudoPage, - line: line, - col: col - }); - }, - - //CSS3 Paged Media - _margin: function() { - /* - * margin : - * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - marginSym = this._margin_sym(); - - if (marginSym) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this.fire({ - type: "startpagemargin", - margin: marginSym, - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endpagemargin", - margin: marginSym, - line: line, - col: col - }); - return true; - } else { - return false; - } - }, - - //CSS3 Paged Media - _margin_sym: function() { - - /* - * margin_sym : - * TOPLEFTCORNER_SYM | - * TOPLEFT_SYM | - * TOPCENTER_SYM | - * TOPRIGHT_SYM | - * TOPRIGHTCORNER_SYM | - * BOTTOMLEFTCORNER_SYM | - * BOTTOMLEFT_SYM | - * BOTTOMCENTER_SYM | - * BOTTOMRIGHT_SYM | - * BOTTOMRIGHTCORNER_SYM | - * LEFTTOP_SYM | - * LEFTMIDDLE_SYM | - * LEFTBOTTOM_SYM | - * RIGHTTOP_SYM | - * RIGHTMIDDLE_SYM | - * RIGHTBOTTOM_SYM - * ; - */ - - var tokenStream = this._tokenStream; - - if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, - Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, - Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, - Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, - Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, - Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, - Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) { - return SyntaxUnit.fromToken(tokenStream.token()); - } else { - return null; - } - }, - - _pseudo_page: function() { - /* - * pseudo_page - * : ':' IDENT - * ; - */ - - var tokenStream = this._tokenStream; - - tokenStream.mustMatch(Tokens.COLON); - tokenStream.mustMatch(Tokens.IDENT); - - //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed - - return tokenStream.token().value; - }, - - _font_face: function() { - /* - * font_face - * : FONT_FACE_SYM S* - * '{' S* declaration [ ';' S* declaration ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - //look for @page - tokenStream.mustMatch(Tokens.FONT_FACE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - this.fire({ - type: "startfontface", - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endfontface", - line: line, - col: col - }); - }, - - _viewport: function() { - /* - * viewport - * : VIEWPORT_SYM S* - * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - tokenStream.mustMatch(Tokens.VIEWPORT_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - this.fire({ - type: "startviewport", - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endviewport", - line: line, - col: col - }); - }, - - _document: function() { - /* - * document - * : DOCUMENT_SYM S* - * _document_function [ ',' S* _document_function ]* S* - * '{' S* ruleset* '}' - * ; - */ - - var tokenStream = this._tokenStream, - token, - functions = [], - prefix = ""; - - tokenStream.mustMatch(Tokens.DOCUMENT_SYM); - token = tokenStream.token(); - if (/^@\-([^\-]+)\-/.test(token.value)) { - prefix = RegExp.$1; - } - - this._readWhitespace(); - functions.push(this._document_function()); - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - functions.push(this._document_function()); - } - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - this.fire({ - type: "startdocument", - functions: functions, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - var ok = true; - while (ok) { - switch (tokenStream.peek()) { - case Tokens.PAGE_SYM: - this._page(); - break; - case Tokens.FONT_FACE_SYM: - this._font_face(); - break; - case Tokens.VIEWPORT_SYM: - this._viewport(); - break; - case Tokens.MEDIA_SYM: - this._media(); - break; - case Tokens.KEYFRAMES_SYM: - this._keyframes(); - break; - case Tokens.DOCUMENT_SYM: - this._document(); - break; - default: - ok = Boolean(this._ruleset()); - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - token = tokenStream.token(); - this._readWhitespace(); - - this.fire({ - type: "enddocument", - functions: functions, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - }, - - _document_function: function() { - /* - * document_function - * : function | URI S* - * ; - */ - - var tokenStream = this._tokenStream, - value; - - if (tokenStream.match(Tokens.URI)) { - value = tokenStream.token().value; - this._readWhitespace(); - } else { - value = this._function(); - } - - return value; - }, - - _operator: function(inFunction) { - - /* - * operator (outside function) - * : '/' S* | ',' S* | /( empty )/ - * operator (inside function) - * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ - * ; - */ - - var tokenStream = this._tokenStream, - token = null; - - if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || - (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) { - token = tokenStream.token(); - this._readWhitespace(); - } - return token ? PropertyValuePart.fromToken(token) : null; - }, - - _combinator: function() { - - /* - * combinator - * : PLUS S* | GREATER S* | TILDE S* | S+ - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - token; - - if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) { - token = tokenStream.token(); - value = new Combinator(token.value, token.startLine, token.startCol); - this._readWhitespace(); - } - - return value; - }, - - _unary_operator: function() { - - /* - * unary_operator - * : '-' | '+' - * ; - */ - - var tokenStream = this._tokenStream; - - if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) { - return tokenStream.token().value; - } else { - return null; - } - }, - - _property: function() { - - /* - * property - * : IDENT S* - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - hack = null, - tokenValue, - token, - line, - col; - - //check for star hack - throws error if not allowed - if (tokenStream.peek() === Tokens.STAR && this.options.starHack) { - tokenStream.get(); - token = tokenStream.token(); - hack = token.value; - line = token.startLine; - col = token.startCol; - } - - if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - tokenValue = token.value; - - //check for underscore hack - no error if not allowed because it's valid CSS syntax - if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) { - hack = "_"; - tokenValue = tokenValue.substring(1); - } - - value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); - this._readWhitespace(); - } - - return value; - }, - - //Augmented with CSS3 Selectors - _ruleset: function() { - /* - * ruleset - * : selectors_group - * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ - - var tokenStream = this._tokenStream, - tt, - selectors; - - - /* - * Error Recovery: If even a single selector fails to parse, - * then the entire ruleset should be thrown away. - */ - try { - selectors = this._selectors_group(); - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - - //skip over everything until closing brace - tt = tokenStream.advance([Tokens.RBRACE]); - if (tt === Tokens.RBRACE) { - //if there's a right brace, the rule is finished so don't do anything - } else { - //otherwise, rethrow the error because it wasn't handled properly - throw ex; - } - } else { - //not a syntax error, rethrow it - throw ex; - } - - //trigger parser to continue - return true; - } - - //if it got here, all selectors parsed - if (selectors) { - - this.fire({ - type: "startrule", - selectors: selectors, - line: selectors[0].line, - col: selectors[0].col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endrule", - selectors: selectors, - line: selectors[0].line, - col: selectors[0].col - }); - } - - return selectors; - }, - - //CSS3 Selectors - _selectors_group: function() { - - /* - * selectors_group - * : selector [ COMMA S* selector ]* - * ; - */ - var tokenStream = this._tokenStream, - selectors = [], - selector; - - selector = this._selector(); - if (selector !== null) { - - selectors.push(selector); - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - selector = this._selector(); - if (selector !== null) { - selectors.push(selector); - } else { - this._unexpectedToken(tokenStream.LT(1)); - } - } - } - - return selectors.length ? selectors : null; - }, - - //CSS3 Selectors - _selector: function() { - /* - * selector - * : simple_selector_sequence [ combinator simple_selector_sequence ]* - * ; - */ - - var tokenStream = this._tokenStream, - selector = [], - nextSelector = null, - combinator = null, - ws = null; - - //if there's no simple selector, then there's no selector - nextSelector = this._simple_selector_sequence(); - if (nextSelector === null) { - return null; - } - - selector.push(nextSelector); - - do { - - //look for a combinator - combinator = this._combinator(); - - if (combinator !== null) { - selector.push(combinator); - nextSelector = this._simple_selector_sequence(); - - //there must be a next selector - if (nextSelector === null) { - this._unexpectedToken(tokenStream.LT(1)); - } else { - - //nextSelector is an instance of SelectorPart - selector.push(nextSelector); - } - } else { - - //if there's not whitespace, we're done - if (this._readWhitespace()) { - - //add whitespace separator - ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); - - //combinator is not required - combinator = this._combinator(); - - //selector is required if there's a combinator - nextSelector = this._simple_selector_sequence(); - if (nextSelector === null) { - if (combinator !== null) { - this._unexpectedToken(tokenStream.LT(1)); - } - } else { - - if (combinator !== null) { - selector.push(combinator); - } else { - selector.push(ws); - } - - selector.push(nextSelector); - } - } else { - break; - } - } - } while (true); - - return new Selector(selector, selector[0].line, selector[0].col); - }, - - //CSS3 Selectors - _simple_selector_sequence: function() { - /* - * simple_selector_sequence - * : [ type_selector | universal ] - * [ HASH | class | attrib | pseudo | negation ]* - * | [ HASH | class | attrib | pseudo | negation ]+ - * ; - */ - - var tokenStream = this._tokenStream, - - //parts of a simple selector - elementName = null, - modifiers = [], - - //complete selector text - selectorText= "", - - //the different parts after the element name to search for - components = [ - //HASH - function() { - return tokenStream.match(Tokens.HASH) ? - new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : - null; - }, - this._class, - this._attrib, - this._pseudo, - this._negation - ], - i = 0, - len = components.length, - component = null, - line, - col; - - - //get starting line and column for the selector - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - - elementName = this._type_selector(); - if (!elementName) { - elementName = this._universal(); - } - - if (elementName !== null) { - selectorText += elementName; - } - - while (true) { - - //whitespace means we're done - if (tokenStream.peek() === Tokens.S) { - break; - } - - //check for each component - while (i < len && component === null) { - component = components[i++].call(this); - } - - if (component === null) { - - //we don't have a selector - if (selectorText === "") { - return null; - } else { - break; - } - } else { - i = 0; - modifiers.push(component); - selectorText += component.toString(); - component = null; - } - } - - - return selectorText !== "" ? - new SelectorPart(elementName, modifiers, selectorText, line, col) : - null; - }, - - //CSS3 Selectors - _type_selector: function() { - /* - * type_selector - * : [ namespace_prefix ]? element_name - * ; - */ - - var tokenStream = this._tokenStream, - ns = this._namespace_prefix(), - elementName = this._element_name(); - - if (!elementName) { - /* - * Need to back out the namespace that was read due to both - * type_selector and universal reading namespace_prefix - * first. Kind of hacky, but only way I can figure out - * right now how to not change the grammar. - */ - if (ns) { - tokenStream.unget(); - if (ns.length > 1) { - tokenStream.unget(); - } - } - - return null; - } else { - if (ns) { - elementName.text = ns + elementName.text; - elementName.col -= ns.length; - } - return elementName; - } - }, - - //CSS3 Selectors - _class: function() { - /* - * class - * : '.' IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.DOT)) { - tokenStream.mustMatch(Tokens.IDENT); - token = tokenStream.token(); - return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); - } else { - return null; - } - }, - - //CSS3 Selectors - _element_name: function() { - /* - * element_name - * : IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); - } else { - return null; - } - }, - - //CSS3 Selectors - _namespace_prefix: function() { - /* - * namespace_prefix - * : [ IDENT | '*' ]? '|' - * ; - */ - var tokenStream = this._tokenStream, - value = ""; - - //verify that this is a namespace prefix - if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) { - - if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) { - value += tokenStream.token().value; - } - - tokenStream.mustMatch(Tokens.PIPE); - value += "|"; - } - - return value.length ? value : null; - }, - - //CSS3 Selectors - _universal: function() { - /* - * universal - * : [ namespace_prefix ]? '*' - * ; - */ - var tokenStream = this._tokenStream, - value = "", - ns; - - ns = this._namespace_prefix(); - if (ns) { - value += ns; - } - - if (tokenStream.match(Tokens.STAR)) { - value += "*"; - } - - return value.length ? value : null; - }, - - //CSS3 Selectors - _attrib: function() { - /* - * attrib - * : '[' S* [ namespace_prefix ]? IDENT S* - * [ [ PREFIXMATCH | - * SUFFIXMATCH | - * SUBSTRINGMATCH | - * '=' | - * INCLUDES | - * DASHMATCH ] S* [ IDENT | STRING ] S* - * ]? ']' - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - ns, - token; - - if (tokenStream.match(Tokens.LBRACKET)) { - token = tokenStream.token(); - value = token.value; - value += this._readWhitespace(); - - ns = this._namespace_prefix(); - - if (ns) { - value += ns; - } - - tokenStream.mustMatch(Tokens.IDENT); - value += tokenStream.token().value; - value += this._readWhitespace(); - - if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, - Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) { - - value += tokenStream.token().value; - value += this._readWhitespace(); - - tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); - value += tokenStream.token().value; - value += this._readWhitespace(); - } - - tokenStream.mustMatch(Tokens.RBRACKET); - - return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); - } else { - return null; - } - }, - - //CSS3 Selectors - _pseudo: function() { - - /* - * pseudo - * : ':' ':'? [ IDENT | functional_pseudo ] - * ; - */ - - var tokenStream = this._tokenStream, - pseudo = null, - colons = ":", - line, - col; - - if (tokenStream.match(Tokens.COLON)) { - - if (tokenStream.match(Tokens.COLON)) { - colons += ":"; - } - - if (tokenStream.match(Tokens.IDENT)) { - pseudo = tokenStream.token().value; - line = tokenStream.token().startLine; - col = tokenStream.token().startCol - colons.length; - } else if (tokenStream.peek() === Tokens.FUNCTION) { - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol - colons.length; - pseudo = this._functional_pseudo(); - } - - if (pseudo) { - pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); - } else { - var startLine = tokenStream.LT(1).startLine, - startCol = tokenStream.LT(0).startCol; - throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol); - } - } - - return pseudo; - }, - - //CSS3 Selectors - _functional_pseudo: function() { - /* - * functional_pseudo - * : FUNCTION S* expression ')' - * ; - */ - - var tokenStream = this._tokenStream, - value = null; - - if (tokenStream.match(Tokens.FUNCTION)) { - value = tokenStream.token().value; - value += this._readWhitespace(); - value += this._expression(); - tokenStream.mustMatch(Tokens.RPAREN); - value += ")"; - } - - return value; - }, - - //CSS3 Selectors - _expression: function() { - /* - * expression - * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ - * ; - */ - - var tokenStream = this._tokenStream, - value = ""; - - while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, - Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, - Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, - Tokens.RESOLUTION, Tokens.SLASH])) { - - value += tokenStream.token().value; - value += this._readWhitespace(); - } - - return value.length ? value : null; - }, - - //CSS3 Selectors - _negation: function() { - /* - * negation - * : NOT S* negation_arg S* ')' - * ; - */ - - var tokenStream = this._tokenStream, - line, - col, - value = "", - arg, - subpart = null; - - if (tokenStream.match(Tokens.NOT)) { - value = tokenStream.token().value; - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - value += this._readWhitespace(); - arg = this._negation_arg(); - value += arg; - value += this._readWhitespace(); - tokenStream.match(Tokens.RPAREN); - value += tokenStream.token().value; - - subpart = new SelectorSubPart(value, "not", line, col); - subpart.args.push(arg); - } - - return subpart; - }, - - //CSS3 Selectors - _negation_arg: function() { - /* - * negation_arg - * : type_selector | universal | HASH | class | attrib | pseudo - * ; - */ - - var tokenStream = this._tokenStream, - args = [ - this._type_selector, - this._universal, - function() { - return tokenStream.match(Tokens.HASH) ? - new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : - null; - }, - this._class, - this._attrib, - this._pseudo - ], - arg = null, - i = 0, - len = args.length, - line, - col, - part; - - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - - while (i < len && arg === null) { - - arg = args[i].call(this); - i++; - } - - //must be a negation arg - if (arg === null) { - this._unexpectedToken(tokenStream.LT(1)); - } - - //it's an element name - if (arg.type === "elementName") { - part = new SelectorPart(arg, [], arg.toString(), line, col); - } else { - part = new SelectorPart(null, [arg], arg.toString(), line, col); - } - - return part; - }, - - _declaration: function() { - - /* - * declaration - * : property ':' S* expr prio? - * | /( empty )/ - * ; - */ - - var tokenStream = this._tokenStream, - property = null, - expr = null, - prio = null, - invalid = null, - propertyName= ""; - - property = this._property(); - if (property !== null) { - - tokenStream.mustMatch(Tokens.COLON); - this._readWhitespace(); - - expr = this._expr(); - - //if there's no parts for the value, it's an error - if (!expr || expr.length === 0) { - this._unexpectedToken(tokenStream.LT(1)); - } - - prio = this._prio(); - - /* - * If hacks should be allowed, then only check the root - * property. If hacks should not be allowed, treat - * _property or *property as invalid properties. - */ - propertyName = property.toString(); - if (this.options.starHack && property.hack === "*" || - this.options.underscoreHack && property.hack === "_") { - - propertyName = property.text; - } - - try { - this._validateProperty(propertyName, expr); - } catch (ex) { - invalid = ex; - } - - this.fire({ - type: "property", - property: property, - value: expr, - important: prio, - line: property.line, - col: property.col, - invalid: invalid - }); - - return true; - } else { - return false; - } - }, - - _prio: function() { - /* - * prio - * : IMPORTANT_SYM S* - * ; - */ - - var tokenStream = this._tokenStream, - result = tokenStream.match(Tokens.IMPORTANT_SYM); - - this._readWhitespace(); - return result; - }, - - _expr: function(inFunction) { - /* - * expr - * : term [ operator term ]* - * ; - */ - - var values = [], - //valueParts = [], - value = null, - operator = null; - - value = this._term(inFunction); - if (value !== null) { - - values.push(value); - - do { - operator = this._operator(inFunction); - - //if there's an operator, keep building up the value parts - if (operator) { - values.push(operator); - } /*else { - //if there's not an operator, you have a full value - values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); - valueParts = []; - }*/ - - value = this._term(inFunction); - - if (value === null) { - break; - } else { - values.push(value); - } - } while (true); - } - - //cleanup - /*if (valueParts.length) { - values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); - }*/ - - return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; - }, - - _term: function(inFunction) { - - /* - * term - * : unary_operator? - * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | - * TIME S* | FREQ S* | function | ie_function ] - * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor - * ; - */ - - var tokenStream = this._tokenStream, - unary = null, - value = null, - endChar = null, - part = null, - token, - line, - col; - - //returns the operator or null - unary = this._unary_operator(); - if (unary !== null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - - //exception for IE filters - if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) { - - value = this._ie_function(); - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - - //see if it's a simple block - } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) { - - token = tokenStream.token(); - endChar = token.endChar; - value = token.value + this._expr(inFunction).text; - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - tokenStream.mustMatch(Tokens.type(endChar)); - value += endChar; - this._readWhitespace(); - - //see if there's a simple match - } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, - Tokens.ANGLE, Tokens.TIME, - Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { - - value = tokenStream.token().value; - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - // Correct potentially-inaccurate IDENT parsing in - // PropertyValuePart constructor. - part = PropertyValuePart.fromToken(tokenStream.token()); - } - this._readWhitespace(); - } else { - - //see if it's a color - token = this._hexcolor(); - if (token === null) { - - //if there's no unary, get the start of the next token for line/col info - if (unary === null) { - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - } - - //has to be a function - if (value === null) { - - /* - * This checks for alpha(opacity=0) style of IE - * functions. IE_FUNCTION only presents progid: style. - */ - if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) { - value = this._ie_function(); - } else { - value = this._function(); - } - } - - /*if (value === null) { - return null; - //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); - }*/ - } else { - value = token.value; - if (unary === null) { - line = token.startLine; - col = token.startCol; - } - } - } - - return part !== null ? part : value !== null ? - new PropertyValuePart(unary !== null ? unary + value : value, line, col) : - null; - }, - - _function: function() { - - /* - * function - * : FUNCTION S* expr ')' S* - * ; - */ - - var tokenStream = this._tokenStream, - functionText = null, - expr = null, - lt; - - if (tokenStream.match(Tokens.FUNCTION)) { - functionText = tokenStream.token().value; - this._readWhitespace(); - expr = this._expr(true); - functionText += expr; - - //START: Horrible hack in case it's an IE filter - if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) { - do { - - if (this._readWhitespace()) { - functionText += tokenStream.token().value; - } - - //might be second time in the loop - if (tokenStream.LA(0) === Tokens.COMMA) { - functionText += tokenStream.token().value; - } - - tokenStream.match(Tokens.IDENT); - functionText += tokenStream.token().value; - - tokenStream.match(Tokens.EQUALS); - functionText += tokenStream.token().value; - - //functionText += this._term(); - lt = tokenStream.peek(); - while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { - tokenStream.get(); - functionText += tokenStream.token().value; - lt = tokenStream.peek(); - } - } while (tokenStream.match([Tokens.COMMA, Tokens.S])); - } - - //END: Horrible Hack - - tokenStream.match(Tokens.RPAREN); - functionText += ")"; - this._readWhitespace(); - } - - return functionText; - }, - - _ie_function: function() { - - /* (My own extension) - * ie_function - * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* - * ; - */ - - var tokenStream = this._tokenStream, - functionText = null, - lt; - - //IE function can begin like a regular function, too - if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) { - functionText = tokenStream.token().value; - - do { - - if (this._readWhitespace()) { - functionText += tokenStream.token().value; - } - - //might be second time in the loop - if (tokenStream.LA(0) === Tokens.COMMA) { - functionText += tokenStream.token().value; - } - - tokenStream.match(Tokens.IDENT); - functionText += tokenStream.token().value; - - tokenStream.match(Tokens.EQUALS); - functionText += tokenStream.token().value; - - //functionText += this._term(); - lt = tokenStream.peek(); - while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { - tokenStream.get(); - functionText += tokenStream.token().value; - lt = tokenStream.peek(); - } - } while (tokenStream.match([Tokens.COMMA, Tokens.S])); - - tokenStream.match(Tokens.RPAREN); - functionText += ")"; - this._readWhitespace(); - } - - return functionText; - }, - - _hexcolor: function() { - /* - * There is a constraint on the color that it must - * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) - * after the "#"; e.g., "#000" is OK, but "#abcd" is not. - * - * hexcolor - * : HASH S* - * ; - */ - - var tokenStream = this._tokenStream, - token = null, - color; - - if (tokenStream.match(Tokens.HASH)) { - - //need to do some validation here - - token = tokenStream.token(); - color = token.value; - if (!/#[a-f0-9]{3,6}/i.test(color)) { - throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); - } - this._readWhitespace(); - } - - return token; - }, - - //----------------------------------------------------------------- - // Animations methods - //----------------------------------------------------------------- - - _keyframes: function() { - - /* - * keyframes: - * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { - * ; - */ - var tokenStream = this._tokenStream, - token, - tt, - name, - prefix = ""; - - tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); - token = tokenStream.token(); - if (/^@\-([^\-]+)\-/.test(token.value)) { - prefix = RegExp.$1; - } - - this._readWhitespace(); - name = this._keyframe_name(); - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.LBRACE); - - this.fire({ - type: "startkeyframes", - name: name, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - this._readWhitespace(); - tt = tokenStream.peek(); - - //check for key - while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) { - this._keyframe_rule(); - this._readWhitespace(); - tt = tokenStream.peek(); - } - - this.fire({ - type: "endkeyframes", - name: name, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - }, - - _keyframe_name: function() { - - /* - * keyframe_name: - * : IDENT - * | STRING - * ; - */ - var tokenStream = this._tokenStream; - - tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); - return SyntaxUnit.fromToken(tokenStream.token()); - }, - - _keyframe_rule: function() { - - /* - * keyframe_rule: - * : key_list S* - * '{' S* declaration [ ';' S* declaration ]* '}' S* - * ; - */ - var keyList = this._key_list(); - - this.fire({ - type: "startkeyframerule", - keys: keyList, - line: keyList[0].line, - col: keyList[0].col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endkeyframerule", - keys: keyList, - line: keyList[0].line, - col: keyList[0].col - }); - }, - - _key_list: function() { - - /* - * key_list: - * : key [ S* ',' S* key]* - * ; - */ - var tokenStream = this._tokenStream, - keyList = []; - - //must be least one key - keyList.push(this._key()); - - this._readWhitespace(); - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - keyList.push(this._key()); - this._readWhitespace(); - } - - return keyList; - }, - - _key: function() { - /* - * There is a restriction that IDENT can be only "from" or "to". - * - * key - * : PERCENTAGE - * | IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.PERCENTAGE)) { - return SyntaxUnit.fromToken(tokenStream.token()); - } else if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - - if (/from|to/i.test(token.value)) { - return SyntaxUnit.fromToken(token); - } - - tokenStream.unget(); - } - - //if it gets here, there wasn't a valid token, so time to explode - this._unexpectedToken(tokenStream.LT(1)); - }, - - //----------------------------------------------------------------- - // Helper methods - //----------------------------------------------------------------- - - /** - * Not part of CSS grammar, but useful for skipping over - * combination of white space and HTML-style comments. - * @return {void} - * @method _skipCruft - * @private - */ - _skipCruft: function() { - while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) { - //noop - } - }, - - /** - * Not part of CSS grammar, but this pattern occurs frequently - * in the official CSS grammar. Split out here to eliminate - * duplicate code. - * @param {Boolean} checkStart Indicates if the rule should check - * for the left brace at the beginning. - * @param {Boolean} readMargins Indicates if the rule should check - * for margin patterns. - * @return {void} - * @method _readDeclarations - * @private - */ - _readDeclarations: function(checkStart, readMargins) { - /* - * Reads the pattern - * S* '{' S* declaration [ ';' S* declaration ]* '}' S* - * or - * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* - * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. - * A semicolon is only necessary following a declaration if there's another declaration - * or margin afterwards. - */ - var tokenStream = this._tokenStream, - tt; - - - this._readWhitespace(); - - if (checkStart) { - tokenStream.mustMatch(Tokens.LBRACE); - } - - this._readWhitespace(); - - try { - - while (true) { - - if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) { - //noop - } else if (this._declaration()) { - if (!tokenStream.match(Tokens.SEMICOLON)) { - break; - } - } else { - break; - } - - //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ - // break; - //} - this._readWhitespace(); - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - - //see if there's another declaration - tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); - if (tt === Tokens.SEMICOLON) { - //if there's a semicolon, then there might be another declaration - this._readDeclarations(false, readMargins); - } else if (tt !== Tokens.RBRACE) { - //if there's a right brace, the rule is finished so don't do anything - //otherwise, rethrow the error because it wasn't handled properly - throw ex; - } - } else { - //not a syntax error, rethrow it - throw ex; - } - } - }, - - /** - * In some cases, you can end up with two white space tokens in a - * row. Instead of making a change in every function that looks for - * white space, this function is used to match as much white space - * as necessary. - * @method _readWhitespace - * @return {String} The white space if found, empty string if not. - * @private - */ - _readWhitespace: function() { - - var tokenStream = this._tokenStream, - ws = ""; - - while (tokenStream.match(Tokens.S)) { - ws += tokenStream.token().value; - } - - return ws; - }, - - - /** - * Throws an error when an unexpected token is found. - * @param {Object} token The token that was found. - * @method _unexpectedToken - * @return {void} - * @private - */ - _unexpectedToken: function(token) { - throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); - }, - - /** - * Helper method used for parsing subparts of a style sheet. - * @return {void} - * @method _verifyEnd - * @private - */ - _verifyEnd: function() { - if (this._tokenStream.LA(1) !== Tokens.EOF) { - this._unexpectedToken(this._tokenStream.LT(1)); - } - }, - - //----------------------------------------------------------------- - // Validation methods - //----------------------------------------------------------------- - _validateProperty: function(property, value) { - Validation.validate(property, value); - }, - - //----------------------------------------------------------------- - // Parsing methods - //----------------------------------------------------------------- - - parse: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - this._stylesheet(); - }, - - parseStyleSheet: function(input) { - //just passthrough - return this.parse(input); - }, - - parseMediaQuery: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - var result = this._media_query(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a property value (everything after the semicolon). - * @return {parserlib.css.PropertyValue} The property value. - * @throws parserlib.util.SyntaxError If an unexpected token is found. - * @method parserPropertyValue - */ - parsePropertyValue: function(input) { - - this._tokenStream = new TokenStream(input, Tokens); - this._readWhitespace(); - - var result = this._expr(); - - //okay to have a trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a complete CSS rule, including selectors and - * properties. - * @param {String} input The text to parser. - * @return {Boolean} True if the parse completed successfully, false if not. - * @method parseRule - */ - parseRule: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - - //skip any leading white space - this._readWhitespace(); - - var result = this._ruleset(); - - //skip any trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a single CSS selector (no comma) - * @param {String} input The text to parse as a CSS selector. - * @return {Selector} An object representing the selector. - * @throws parserlib.util.SyntaxError If an unexpected token is found. - * @method parseSelector - */ - parseSelector: function(input) { - - this._tokenStream = new TokenStream(input, Tokens); - - //skip any leading white space - this._readWhitespace(); - - var result = this._selector(); - - //skip any trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses an HTML style attribute: a set of CSS declarations - * separated by semicolons. - * @param {String} input The text to parse as a style attribute - * @return {void} - * @method parseStyleAttribute - */ - parseStyleAttribute: function(input) { - input += "}"; // for error recovery in _readDeclarations() - this._tokenStream = new TokenStream(input, Tokens); - this._readDeclarations(); - } - }; - - //copy over onto prototype - for (prop in additions) { - if (Object.prototype.hasOwnProperty.call(additions, prop)) { - proto[prop] = additions[prop]; - } - } - - return proto; -}(); - - -/* -nth - : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | - ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* - ; -*/ -},{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){ -"use strict"; - -/* exported Properties */ - -var Properties = module.exports = { - __proto__: null, - - //A - "align-items" : "flex-start | flex-end | center | baseline | stretch", - "align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", - "align-self" : "auto | flex-start | flex-end | center | baseline | stretch", - "all" : "initial | inherit | unset", - "-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch", - "-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", - "-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch", - "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ", - "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", - "animation" : 1, - "animation-delay" : "