From b4bdfb5f9fd61b2295acc75d8de82848618a76a9 Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Wed, 4 Apr 2018 21:44:52 +0530 Subject: [PATCH] New upstream version 10.6.2+dfsg --- CHANGELOG.md | 34 +- Gemfile | 2 +- Gemfile.lock | 10 +- VERSION | 2 +- app/assets/javascripts/commons/polyfills.js | 1 + app/models/ci/build.rb | 9 +- app/models/group.rb | 4 +- app/models/member.rb | 2 +- app/services/notification_service.rb | 4 +- ...9_add_indexes_for_user_activity_queries.rb | 40 + db/schema.rb | 4 +- doc/ci/README.md | 3 +- doc/ci/caching/index.md | 516 ++++ doc/ci/runners/README.md | 59 +- .../runners/img/shared_runner_ip_address.png | Bin 0 -> 69821 bytes .../img/specific_runner_ip_address.png | Bin 0 -> 42055 bytes doc/ci/yaml/README.md | 179 ++ doc/install/kubernetes/index.md | 4 +- .../merge_requests/maintainer_access.md | 13 +- lib/banzai/filter/autolink_filter.rb | 11 +- lib/gitlab/auth.rb | 6 +- lib/gitlab/auth/database/authentication.rb | 2 +- lib/gitlab/auth/ldap/authentication.rb | 22 +- lib/gitlab/auth/o_auth/authentication.rb | 1 + lib/gitlab/conflict/file_collection.rb | 5 +- lib/gitlab/encoding_helper.rb | 2 +- lib/gitlab/git/conflict/file.rb | 16 +- lib/gitlab/git/conflict/parser.rb | 5 - .../gitaly_client/conflict_files_stitcher.rb | 2 +- .../lib/banzai/filter/autolink_filter_spec.rb | 12 +- spec/lib/gitlab/auth_spec.rb | 14 +- spec/lib/gitlab/ci/trace_spec.rb | 22 + spec/lib/gitlab/encoding_helper_spec.rb | 5 + spec/lib/gitlab/git/conflict/file_spec.rb | 50 + spec/lib/gitlab/git/conflict/parser_spec.rb | 7 - spec/models/ci/build_spec.rb | 29 + vendor/assets/javascripts/xterm/xterm.js | 2235 +++++++++++++++++ 37 files changed, 3235 insertions(+), 97 deletions(-) create mode 100644 db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb create mode 100644 doc/ci/caching/index.md create mode 100644 doc/ci/runners/img/shared_runner_ip_address.png create mode 100644 doc/ci/runners/img/specific_runner_ip_address.png create mode 100644 spec/lib/gitlab/git/conflict/file_spec.rb create mode 100644 vendor/assets/javascripts/xterm/xterm.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1c3a572f..9483423376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.6.2 (2018-03-29) + +### Fixed (2 changes, 1 of them is from the community) + +- Don't capture trailing punctuation when autolinking. !17965 +- Cloning a repository over HTTPS with LDAP credentials causes a HTTP 401 Access denied. (Horatiu Eugen Vlad) + + +## 10.6.1 (2018-03-27) + +### Security (1 change) + +- Bump rails-html-sanitizer to 1.0.4. + +### Fixed (2 changes) + +- Prevent auto-retry AccessDenied error from stopping transition to failed. !17862 +- Fix 500 error when trying to resolve non-ASCII conflicts in the editor. !17962 + +### Performance (1 change) + +- Add indexes for user activity queries. !17890 + +### Other (1 change) + +- Add documentation for runner IP address (#44232). !17837 + + ## 10.6.0 (2018-03-22) ### Security (4 changes) @@ -168,13 +196,17 @@ entry. - Add one group board to Libre. - Add support for filtering by source and target branch to merge requests API. -### Other (14 changes, 3 of them are from the community) +### Other (18 changes, 7 of them are from the community) +- Group MRs on issue page by project and namespace. !8494 (Jeff Stubler) +- Make oauth provider login generic. !8809 (Horatiu Eugen Vlad) +- Add email button to new issue by email. !10942 (Islam Wazery) - Update vue component naming guidelines. !17018 (George Tsiolis) - Added new design for promotion modals. !17197 - Update to github-linguist 5.3.x. !17241 (Ken Ding) - update toml-rb to 1.0.0. !17259 (Ken Ding) - Keep track of projects a user interacted with. !17327 +- Moved o_auth/saml/ldap modules under gitlab/auth. !17359 (Horatiu Eugen Vlad) - Enables eslint in codeclimate job. !17392 - Port Labels Select dropdown to Vue. !17411 - Add NOT NULL constraint to projects.namespace_id. !17448 diff --git a/Gemfile b/Gemfile index bc30c31c38..ac06a938d0 100644 --- a/Gemfile +++ b/Gemfile @@ -218,7 +218,7 @@ gem 'sanitize', '~> 2.0' gem 'babosa', '~> 1.0.2' # Sanitizes SVG input -gem 'loofah', '~> 2.0.3' +gem 'loofah', '~> 2.2' # Working with license gem 'licensee', '~> 8.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index 8957f1f386..41e6f2cc52 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,6 +142,7 @@ GEM connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) + crass (1.0.3) creole (0.5.0) css_parser (1.5.0) addressable @@ -488,7 +489,8 @@ GEM actionpack (>= 4, < 5.2) activesupport (>= 4, < 5.2) railties (>= 4, < 5.2) - loofah (2.0.3) + loofah (2.2.2) + crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.0) mini_mime (>= 0.1.1) @@ -685,8 +687,8 @@ GEM activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) rails-i18n (4.0.9) i18n (~> 0.7) railties (~> 4.0) @@ -1100,7 +1102,7 @@ DEPENDENCIES license_finder (~> 3.1) licensee (~> 8.7.0) lograge (~> 0.5) - loofah (~> 2.0.3) + loofah (~> 2.2) mail_room (~> 0.9.1) method_source (~> 0.8) minitest (~> 5.7.0) diff --git a/VERSION b/VERSION index d1dd3f904c..6842906c6f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.6.0 +10.6.2 diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index 4623272651..d62d3c2365 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -1,4 +1,5 @@ // ECMAScript polyfills +import 'core-js/fn/array/fill'; import 'core-js/fn/array/find'; import 'core-js/fn/array/find-index'; import 'core-js/fn/array/from'; diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5a381bdd6a..ae24758112 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -140,7 +140,11 @@ module Ci next if build.retries_max.zero? if build.retries_count < build.retries_max - Ci::Build.retry(build, build.user) + begin + Ci::Build.retry(build, build.user) + rescue Gitlab::Access::AccessDeniedError => ex + Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" + end end end @@ -328,8 +332,7 @@ module Ci end def erase_old_trace! - write_attribute(:trace, nil) - save + update_column(:trace, nil) end def needs_touch? diff --git a/app/models/group.rb b/app/models/group.rb index 8d183006c6..f669b1a700 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -230,13 +230,13 @@ class Group < Namespace end GroupMember - .active_without_invites + .active_without_invites_and_requests .where(source_id: source_ids) end def members_with_descendants GroupMember - .active_without_invites + .active_without_invites_and_requests .where(source_id: self_and_descendants.reorder(nil).select(:id)) end diff --git a/app/models/member.rb b/app/models/member.rb index ec8156bbb0..e1a3214853 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -52,7 +52,7 @@ class Member < ActiveRecord::Base end # Like active, but without invites. For when a User is required. - scope :active_without_invites, -> do + scope :active_without_invites_and_requests, -> do left_join_users .where(users: { state: 'active' }) .non_request diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ab94db2c1e..d7d2cde100 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -208,9 +208,9 @@ class NotificationService def new_access_request(member) return true unless member.notifiable?(:subscription) - recipients = member.source.members.active_without_invites.owners_and_masters + recipients = member.source.members.active_without_invites_and_requests.owners_and_masters if fallback_to_group_owners_masters?(recipients, member) - recipients = member.source.group.members.active_without_invites.owners_and_masters + recipients = member.source.group.members.active_without_invites_and_requests.owners_and_masters end recipients.each { |recipient| deliver_access_request_email(recipient, member) } diff --git a/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb b/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb new file mode 100644 index 0000000000..824bbb3ac0 --- /dev/null +++ b/db/migrate/20180320182229_add_indexes_for_user_activity_queries.rb @@ -0,0 +1,40 @@ +class AddIndexesForUserActivityQueries < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :events, [:author_id, :project_id] unless index_exists?(:events, [:author_id, :project_id]) + add_concurrent_index :user_interacted_projects, :user_id unless index_exists?(:user_interacted_projects, :user_id) + end + + def down + remove_concurrent_index :events, [:author_id, :project_id] if index_exists?(:events, [:author_id, :project_id]) + + patch_foreign_keys do + remove_concurrent_index :user_interacted_projects, :user_id if index_exists?(:user_interacted_projects, :user_id) + end + end + + private + + def patch_foreign_keys + return yield if Gitlab::Database.postgresql? + + # MySQL doesn't like to remove the index with a foreign key using it. + remove_foreign_key :user_interacted_projects, :users if fk_exists?(:user_interacted_projects, :user_id) + + yield + + # Let's re-add the foreign key using the existing index on (user_id, project_id) + add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id unless fk_exists?(:user_interacted_projects, :user_id) + end + + def fk_exists?(table, column) + foreign_keys(table).any? do |key| + key.options[:column] == column.to_s + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 70d4a3b6de..d59316faab 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180309160427) do +ActiveRecord::Schema.define(version: 20180320182229) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -728,6 +728,7 @@ ActiveRecord::Schema.define(version: 20180309160427) do end add_index "events", ["action"], name: "index_events_on_action", using: :btree + add_index "events", ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id", using: :btree add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree add_index "events", ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree add_index "events", ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree @@ -1856,6 +1857,7 @@ ActiveRecord::Schema.define(version: 20180309160427) do end add_index "user_interacted_projects", ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree + add_index "user_interacted_projects", ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree create_table "user_synced_attributes_metadata", force: :cascade do |t| t.boolean "name_synced", default: false diff --git a/doc/ci/README.md b/doc/ci/README.md index 532ae52a18..6aa0e5885d 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -65,7 +65,8 @@ learn how to leverage its potential even more. environments and use them for different purposes like testing, building and deploying - [Job artifacts](../user/project/pipelines/job_artifacts.md) -- [Git submodules](git_submodules.md): How to run your CI jobs when Git +- [Caching dependencies](caching/index.md) +- [Git submodules](git_submodules.md) - How to run your CI jobs when Git submodules are involved - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger pipelines through the GitLab API](triggers/README.md) diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md new file mode 100644 index 0000000000..c4b2a25d4a --- /dev/null +++ b/doc/ci/caching/index.md @@ -0,0 +1,516 @@ +# Cache dependencies in GitLab CI/CD + +GitLab CI/CD provides a caching mechanism that can be used to save time +when your jobs are running. + +Caching is about speeding the time a job is executed by reusing the same +content of a previous job. It can be particularly useful when your are +developing software that depends on other libraries which are fetched via the +internet during build time. + +If caching is enabled, it's shared between pipelines and jobs by default, +starting from GitLab 9.0. + +Make sure you read the [`cache` reference](../yaml/README.md#cache) to learn +how it is defined in `.gitlab-ci.yml`. + +## Good caching practices + +We have the cache from the perspective of the developers (who consume a cache +within the job) and the cache from the perspective of the Runner. Depending on +which type of Runner you are using, cache can act differently. + +From the perspective of the developer, to ensure maximum availability of the +cache, when declaring `cache` in your jobs, use one or a mix of the following: + +- [Tag your Runners](../runners/README.md#using-tags) and use the tag on jobs + that share their cache. +- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects) + that will be only available to a particular project. +- [Use a `key`](../yaml/README.md#cache-key) that fits your workflow (e.g., + different caches on each branch). For that, you can take advantage of the + [CI/CD predefined variables](../variables/README.md#predefined-variables-environment-variables). + +TIP: **Tip:** +Using the same Runner for your pipeline, is the most simple and efficient way to +cache files in one stage or pipeline, and pass this cache to subsequent stages +or pipelines in a guaranteed manner. + +From the perspective of the Runner, in order for cache to work effectively, one +of the following must be true: + +- Use a single Runner for all your jobs +- Use multiple Runners (in autoscale mode or not) that use + [distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching), + where the cache is stored in S3 buckets (like shared Runners on GitLab.com) +- Use multiple Runners (not in autoscale mode) of the same architecture that + share a common network-mounted directory (using NFS or something similar) + where the cache will be stored + +TIP: **Tip:** +Read about the [availability of the cache](#availability-of-the-cache) +to learn more about the internals and get a better idea how cache works. + +### Sharing caches across the same branch + +Define a cache with the `key: ${CI_COMMIT_REF_SLUG}` so that jobs of each +branch always use the same cache: + +```yaml +cache: + key: ${CI_COMMIT_REF_SLUG} +``` + +While this feels like it might be safe from accidentally overwriting the cache, +it means merge requests get slow first pipelines, which might be a bad +developer experience. The next time a new commit is pushed to the branch, the +cache will be re-used. + +To enable per-job and per-branch caching: + +```yaml +cache: + key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" +``` + +To enable per-branch and per-stage caching: + +```yaml +cache: + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" +``` + +### Sharing caches across different branches + +If the files you are caching need to be shared across all branches and all jobs, +you can use the same key for all of them: + +```yaml +cache: + key: one-key-to-rull-them-all +``` + +To share the same cache between branches, but separate them by job: + +```yaml +cache: + key: ${CI_JOB_NAME} +``` + +### Disabling cache on specific jobs + +If you have defined the cache globally, it means that each job will use the +same definition. You can override this behavior per-job, and if you want to +disable it completely, use an empty hash: + +```yaml +job: + cache: {} +``` + +For more fine tuning, read also about the +[`cache: policy`](../yaml/README.md#cache-policy). + +## Common use cases + +The most common use case of cache is to preserve contents between subsequent +runs of jobs for things like dependencies and commonly used libraries +(Nodejs packages, PHP packages, rubygems, python libraries, etc.), +so they don't have to be re-fetched from the public internet. + +NOTE: **Note:** +For more examples, check the [GitLab CI Yml](https://gitlab.com/gitlab-org/gitlab-ci-yml) +project. + +### Caching Nodejs dependencies + +Assuming your project is using [npm](https://www.npmjs.com/) or +[Yarn](https://yarnpkg.com/en/) to install the Nodejs dependencies, the +following example defines `cache` globally so that all jobs inherit it. +Nodejs modules are installed in `node_modules/` and are cached per-branch: + +```yaml +# +# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Nodejs.gitlab-ci.yml +# +image: node:latest + +# Cache modules in between jobs +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - node_modules/ + +before_script: + - npm install + +test_async: + script: + - node ./specs/start.js ./specs/async.spec.js +``` + +### Caching PHP dependencies + +Assuming your project is using [Composer](https://getcomposer.org/) to install +the PHP dependencies, the following example defines `cache` globally so that +all jobs inherit it. PHP libraries modules are installed in `vendor/` and +are cached per-branch: + +```yaml +# +# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/PHP.gitlab-ci.yml +# +image: php:7.2 + +# Cache libraries in between jobs +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - vendor/ + +before_script: +# Install and run Composer +- curl --show-error --silent https://getcomposer.org/installer | php +- php composer.phar install + +test: + script: + - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never +``` + +### Caching Python dependencies + +Assuming your project is using [pip](https://pip.pypa.io/en/stable/) to install +the python dependencies, the following example defines `cache` globally so that +all jobs inherit it. Python libraries are installed in a virtualenv under `venv/`, +pip's cache is defined under `.cache/pip/` and both are cached per-branch: + +```yaml +# +# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Python.gitlab-ci.yml +# +image: python:latest + +# Change pip's cache directory to be inside the project directory since we can +# only cache local items. +variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache" + +# Pip's cache doesn't store the python packages +# https://pip.pypa.io/en/stable/reference/pip_install/#caching +# +# If you want to also cache the installed packages, you have to install +# them in a virtualenv and cache it as well. +cache: + paths: + - .cache/ + - venv/ + +before_script: + - python -V # Print out python version for debugging + - pip install virtualenv + - virtualenv venv + - source venv/bin/activate + +test: + script: + - python setup.py test + - pip install flake8 + - flake8 . +``` + +### Caching Ruby dependencies + +Assuming your project is using [Bundler](https://bundler.io) to install the +gem dependencies, the following example defines `cache` globally so that all +jobs inherit it. Gems are installed in `vendor/ruby/` and are cached per-branch: + +```yaml +# +# https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Ruby.gitlab-ci.yml +# +image: ruby:2.5 + +# Cache gems in between builds +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - vendor/ruby + +before_script: + - ruby -v # Print out ruby version for debugging + - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image + - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby + +rspec: + script: + - rspec spec +``` + +## Availability of the cache + +Caching is an optimization, but isn't guaranteed to always work, so you need to +be prepared to regenerate any cached files in each job that needs them. + +Assuming you have properly [defined `cache` in `.gitlab-ci.yml`](../yaml/README.md#cache) +according to your workflow, the availability of the cache ultimately depends on +how the Runner has been configured (the executor type and whether different +Runners are used for passing the cache between jobs). + +### Where the caches are stored + +Since the Runner is the one responsible for storing the cache, it's essential +to know **where** it's stored. All the cache paths defined under a job in +`.gitlab-ci.yml` are archived in a single `cache.zip` file and stored in the +Runner's configured cache location. By default, they are stored locally in the +machine where the Runner is installed and depends on the type of the executor. + +| GitLab Runner executor | Default path of the cache | +| ---------------------- | ------------------------- | +| [Shell](https://docs.gitlab.com/runner/executors/shell.html) | Locally, stored under the `gitlab-runner` user's home directory: `/home/gitlab-runner/cache////cache.zip`. | +| [Docker](https://docs.gitlab.com/runner/executors/docker.html) | Locally, stored under [Docker volumes](https://docs.gitlab.com/runner/executors/docker.html#the-builds-and-cache-storage): `/var/lib/docker/volumes//_data////cache.zip`. | +| [Docker machine](https://docs.gitlab.com/runner/executors/docker_machine.html) (autoscale Runners) | Behaves the same as the Docker executor. | + +### How archiving and extracting works + +In the most simple scenario, consider that you use only one machine where the +Runner is installed, and all jobs of your project run on the same host. + +Let's see the following example of two jobs that belong to two consecutive +stages: + +```yaml +stages: +- build +- test + +before_script: +- echo "Hello" + +job A: + stage: build + script: + - mkdir vendor/ + - echo "build" > vendor/hello.txt + cache: + key: build-cache + paths: + - vendor/ + after_script: + - echo "World" + +job B: + stage: test + script: + - cat vendor/hello.txt + cache: + key: build-cache +``` + +Here's what happens behind the scenes: + +1. Pipeline starts +1. `job A` runs +1. `before_script` is executed +1. `script` is executed +1. `after_script` is executed +1. `cache` runs and the `vendor/` directory is zipped into `cache.zip`. + This file is then saved in the directory based on the + [Runner's setting](#where-the-caches-are-stored) and the `cache: key`. +1. `job B` runs +1. The cache is extracted (if found) +1. `before_script` is executed +1. `script` is executed +1. Pipeline finishes + +By using a single Runner on a single machine, you'll not have the issue where +`job B` might execute on a Runner different from `job A`, thus guaranteeing the +cache between stages. That will only work if the build goes from stage `build` +to `test` in the same Runner/machine, otherwise, you [might not have the cache +available](#cache-mismatch). + +During the caching process, there's also a couple of things to consider: + +- If some other job, with another cache configuration had saved its + cache in the same zip file, it is overwritten. If the S3 based shared cache is + used, the file is additionally uploaded to S3 to an object based on the cache + key. So, two jobs with different paths, but the same cache key, will overwrite + their cache. +- When extracting the cache from `cache.zip`, everything in the zip file is + extracted in the job's working directory (usually the repository which is + pulled down), and the Runner doesn't mind if the archive of `job A` overwrites + things in the archive of `job B`. + +The reason why it works this way is because the cache created for one Runner +often will not be valid when used by a different one which can run on a +**different architecture** (e.g., when the cache includes binary files). And +since the different steps might be executed by Runners running on different +machines, it is a safe default. + +### Cache mismatch + +In the following table, you can see some reasons where you might hit a cache +mismatch and a few ideas how to fix it. + +| Reason of a cache mismatch | How to fix it | +| -------------------------- | ------------- | +| You use multiple standalone Runners (not in autoscale mode) attached to one project without a shared cache | Use only one Runner for your project or use multiple Runners with distributed cache enabled | +| You use Runners in autoscale mode without a distributed cache enabled | Configure the autoscale Runner to use a distributed cache | +| The machine the Runner is installed on is low on disk space or, if you've set up distributed cache, the S3 bucket where the cache is stored doesn't have enough space | Make sure you clear some space to allow new caches to be stored. Currently, there's no automatic way to do this. | +| You use the same `key` for jobs where they cache different paths. | Use different cache keys to that the cache archive is stored to a different location and doesn't overwrite wrong caches. | + +Let's explore some examples. + +--- + +Let's assume you have only one Runner assigned to your project, so the cache +will be stored in the Runner's machine by default. If two jobs, A and B, +have the same cache key, but they cache different paths, cache B would overwrite +cache A, even if their `paths` don't match: + +We want `job A` and `job B` to re-use their +cache when the pipeline is run for a second time. + +```yaml +stages: +- build +- test + +job A: + stage: build + script: make build + cache: + key: same-key + paths: + - public/ + +job B: + stage: test + script: make test + cache: + key: same-key + paths: + - vendor/ +``` + +1. `job A` runs +1. `public/` is cached as cache.zip +1. `job B` runs +1. The previous cache, if any, is unzipped +1. `vendor/` is cached as cache.zip and overwrites the previous one +1. The next time `job A` runs it will use the cache of `job B` which is different + and thus will be ineffective + +To fix that, use different `keys` for each job. + +--- + +In another case, let's assume you have more than one Runners assigned to your +project, but the distributed cache is not enabled. We want the second time the +pipeline is run, `job A` and `job B` to re-use their cache (which in this case +will be different): + +```yaml +stages: +- build +- test + +job A: + stage: build + script: build + cache: + key: keyA + paths: + - vendor/ + +job B: + stage: test + script: test + cache: + key: keyB + paths: + - vendor/ +``` + +In that case, even if the `key` is different (no fear of overwriting), you +might experience the cached files to "get cleaned" before each stage if the +jobs run on different Runners in the subsequent pipelines. + +## Clearing the cache + +GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution +of your jobs by reusing existing data. This however, can sometimes lead to an +inconsistent behavior. + +To start with a fresh copy of the cache, there are two ways to do that. + +### Clearing the cache by changing `cache:key` + +All you have to do is set a new `cache: key` in your `.gitlab-ci.yml`. In the +next run of the pipeline, the cache will be stored in a different location. + +### Clearing the cache manually + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4. + +If you want to avoid editing `.gitlab-ci.yml`, you can easily clear the cache +via GitLab's UI. This will have an impact on all caches of your project as +name of the cache directory will be renamed by appending an integer to it +(`-1`, `-2`, etc.): + +1. Navigate to your project's **CI/CD > Pipelines** page. +1. Click on the **Clear Runner caches** to clean up the cache. +1. On the next push, your CI/CD job will use a new cache. + +Behind the scenes, this works by increasing a counter in the database, and the +value of that counter is used to create the key for the cache. After a push, a +new key is generated and the old cache is not valid anymore. + +## Cache vs artifacts + +NOTE: **Note:** +Be careful if you use cache and artifacts to store the same path in your jobs +as **caches are restored before artifacts** and the content would be overwritten. + +Don't mix the caching with passing artifacts between stages. Caching is not +designed to pass artifacts between stages. Cache is for runtime dependencies +needed to compile the project: + +- `cache` - **Use for temporary storage for project dependencies.** Not useful + for keeping intermediate build results, like `jar` or `apk` files. + Cache was designed to be used to speed up invocations of subsequent runs of a + given job, by keeping things like dependencies (e.g., npm packages, Go vendor + packages, etc.) so they don't have to be re-fetched from the public internet. + While the cache can be abused to pass intermediate build results between stages, + there may be cases where artifacts are a better fit. +- `artifacts` - **Use for stage results that will be passed between stages.** + Artifacts were designed to upload some compiled/generated bits of the build, + and they can be fetched by any number of concurrent Runners. They are + guaranteed to be available and are there to pass data between jobs. They are + also exposed to be downloaded from the UI. + +It's sometimes confusing because the name artifact sounds like something that +is only useful outside of the job, like for downloading a final image. But +artifacts are also available in between stages within a pipeline. So if you +build your application by downloading all the required modules, you might want +to declare them as artifacts so that each subsequent stage can depend on them +being there. There are some optimizations like declaring an +[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts +around too long, and using [dependencies](../yaml/README.md#dependencies) to +control exactly where artifacts are passed around. + +So, to sum up: +- Caches are disabled if not defined globally or per job (using `cache:`) +- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally +- Caches can be used by subsequent pipelines of that very same job (a script in + a stage) in which the cache was created (if not defined globally). +- Caches are stored where the Runner is installed **and** uploaded to S3 if + [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) +- Caches defined per job are only used either a) for the next pipeline of that job, + or b) if that same cache is also defined in a subsequent job of the same pipeline +- Artifacts are disabled if not defined per job (using `artifacts:`) +- Artifacts can only be enabled per job, not globally +- Artifacts are created during a pipeline and can be used by the subsequent + jobs of that currently active pipeline +- Artifacts are always uploaded to GitLab (known as coordinator) +- Artifacts can have an expiration value for controlling disk usage (30 days by default) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index f879ed6201..7a7b50b294 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -146,24 +146,7 @@ To protect/unprotect Runners: ## Manually clearing the Runners cache -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4. - -GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution -of your jobs by reusing existing data. This however, can sometimes lead to an -inconsistent behavior. - -To start with a fresh copy of the cache, you can easily do it via GitLab's UI: - -1. Navigate to your project's **CI/CD > Pipelines** page. -1. Click on the **Clear Runner caches** to clean up the cache. -1. On the next push, your CI/CD job will use a new cache. - -That way, you don't have to change the [cache key](../yaml/README.md#cache-key) -in your `.gitlab-ci.yml`. - -Behind the scenes, this works by increasing a counter in the database, and the -value of that counter is used to create the key for the cache. After a push, a -new key is generated and the old cache is not valid anymore. +Read [clearing the cache](../caching/index.md#clearing-the-cache). ## How shared Runners pick jobs @@ -227,15 +210,16 @@ that it may encounter on the projects it's shared over. This would be problematic for large amounts of projects, if it wasn't for tags. By tagging a Runner for the types of jobs it can handle, you can make sure -shared Runners will only run the jobs they are equipped to run. +shared Runners will [only run the jobs they are equipped to run](../yaml/README.md#tags). For instance, at GitLab we have Runners tagged with "rails" if they contain the appropriate dependencies to run Rails test suites. ### Preventing Runners with tags from picking jobs without tags -You can configure a Runner to prevent it from picking jobs with tags when -the Runner does not have tags assigned. This setting can be enabled the first +You can configure a Runner to prevent it from picking +[jobs with tags](../yaml/README.md#tags) when the Runner does not have tags +assigned. This setting can be enabled the first time you [register a Runner][register] and can be changed afterwards under each Runner's settings. @@ -280,3 +264,36 @@ We're always looking for contributions that can mitigate these [register]: http://docs.gitlab.com/runner/register/ [protected branches]: ../../user/project/protected_branches.md [protected tags]: ../../user/project/protected_tags.md + +## Determining the IP address of a Runner + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6. + +It may be useful to know the IP address of a Runner so you can troubleshoot +issues with that Runner. GitLab stores and displays the IP address by viewing +the source of the HTTP requests it makes to GitLab when polling for jobs. The +IP address is always kept up to date so if the Runner IP changes it will be +automatically updated in GitLab. + +The IP address for shared Runners and specific Runners can be found in +different places. + +### Shared Runners + +To view the IP address of a shared Runner you must have admin access to +the GitLab instance. To determine this: + +1. Visit **Admin area ➔ Overview ➔ Runners** +1. Look for the Runner in the table and you should see a column for "IP Address" + +![shared Runner IP address](img/shared_runner_ip_address.png) + +### Specific Runners + +You can find the IP address of a Runner for a specific project by: + +1. Visit your project's **Settings ➔ CI/CD** +1. Find the Runner and click on it's ID which links you to the details page +1. On the details page you should see a row for "IP Address" + +![specific Runner IP address](img/specific_runner_ip_address.png) diff --git a/doc/ci/runners/img/shared_runner_ip_address.png b/doc/ci/runners/img/shared_runner_ip_address.png new file mode 100644 index 0000000000000000000000000000000000000000..3b1542d59d39d9b36003549b47a350109a5bde2c GIT binary patch literal 69821 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ê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

P#*e;>{nZw?4>^Ktbgu>*lu4ppU0U_?v9T2;I1 zf7j~r?8aLgaQP;uP1~n@h2OT3?p-k7iMrN5bbZu|EoIh^@gYBK*m!%#p{7l1X-jA@ z{{hT&_LcHmH?ZdonUylFLIG>;Vs=mhc@>NoTajOQnt7WT?>Gr6|?q0ydhd&x&zfeuBEx(--`56^sj5e4X_2E)$^ z$rcqFjTs)`tA8Prr5 zWO$$r@mh$p<5MUc24o07L+AO$?p^05-_`&7<=t^MN1ZEOn^}^8c%y_HaxrL;?YS0| zT+I7RpyL{tmS{AUNEnux46xJ2>4#sbY>=@v#X`s((3&YqT~*#}>svO%Ee_iSV2kpK z_H{veUCV}eql9!{HA6h;^GN!X_ZA_|*|Q5Sp&NVom;g#?F6mhgEZ^6|X5Wo(q|0*r zA^m<8N>@gseXg8CRsU|H98OzhC()xfsN5MtY!xpXo?5{iJ}FRwOXT@>Pv64X%56lo zJ<~r;*xTBKoXCf_O87_1!H zDI;GAlXz4CO#q0a8$Z?PxQNwxs978L|ccf4S8Dp)doQxm?AS>V7dZ|DiD_m<|A)X-#rn9KiEL~J@j@U!Qajl z^cP0@7KX8%R$DKreCat29qX6G97Sl_K}#(%p`ZOAqCP+Rn;ZSG{TKwaob7 zGA%V_)Xo(UTR@B-&Lm+F;LdKW`bD#q?YuZ%)8HIdSRO%u4V5_*3U4eYsknxpCBdH* z`d29~JG3ih^IFQW*H>=f%f;IoT!Pf`=^HY0{MTOcIWqFSVT5IVY27CAsY`Qxq<5li z^r!bp-ndce7tJwkT+s$A$r=n(QT+w-p^(9l*}4a+Wd}HVQZkOml&5XsppC?&@FqfL z5EvipKTh#;4_fTCB-hO(vo(WhWvw(o%5+7R8p8ruequx46j3)C3w-KS*>M%`11o2; z9T_vr^X(f)T)mQ}4T&s2~A*RVYO2yJQ>DKNX7~fuvQ5|9^3D*J|{W_ za-j>b`MJ5v;g*$H{n%9>Fv5%sj2lgMTHNo$3B~&pmS2qCC{6v)XW~eF7r*QFrR)t> z!M-{+7(SUtAl#cCfiPKU3MFU=mX1{I{*q7xiO|BLTq?S)Dn;U6Nra5Qcpy0PE+!kK zn5x1*SNrDs-W1{9u~qZ}(89~t3lLYI{PjX=>?5{&)}~a_=F6RLsXV$zX;Q>Wl=}(K zN=0CkMNQDWCCpQnnO<{a7WE@VXgj%LvTrfbn7qM`9Ai7Yl*+D3T=dp^iEuV!Cy9va zM+ynlgFfE0^is9v61l+z<3s2r^83X46su>W@R_E&JPrm(-iDnvDp%v$n|?K;yuq3i z{ldb4jQ}Ck!C^=nOK9#`AU}l#0BsULP$(GlR?{;qfT|lL%zr#5L@F%E5(}T!VL*7y zqrH1xUJPv9JT`I{cVop5;iwz61r$OExIr;US@u?q`x1}%i)1-H%XPC&{FG9$?KR+P z=xs@GAC@SRJy-_<9a-tD;BRS)nxd*5XoMo%(L@&yrXyMxS(LD36qS$32~2YLibV0I zAGjWL+dZ84hF@3X$NnuxR!Er~z7W2S4nQnus}ChaKNGCg}=;cSreNZ;(o z3cHNRF>hjW6}|W)2wr)1S$vPUU+#GQd!xN`!YkP@K=KyP^aPp7!1WcXp+k(;wK2?a zXTAAILylN>{4<(j!{mZ=gE#!WqC9#Dgv(^WBS8-|+v){+T}n-FuzVK=miRT2aS0OT z{QNYx9aMmBR)Tqjub$@;bz<7K*MjN4`0)FmAQzzOAKKdP*v-%MEkL4K@cI2e@_9I) z<;-!dR~IqW#}dsx6TLzIVac_;^IngbASkGMaW1AdYSpQNGp+&vbuXJjs-);fxs8JW zVRswI=TKx-fWWe4`a09psSi#zS0lt6_$_HG?K~G~LJBYgR11^=Hm<(zxq1Y474d35 z*2wdm?vqOOWgm@|!j!3vZ3dmcf+*klGpW(uWmHQFU-uw+XBr0chspBO!-0}U+9d^V zd^3hkxY#!rNiEUMa7Z+MrQ!5ixTMfl%)hlviohK6R`qy1D$AOPgTC@6{MOK|z05Wv z!#3dBroW)v{6ksKo|bq}5^Dv7&Yapcux}*te)Ld-cZLI2wc(gBQ}#ZZ>$|2)`c-1r zyv}>bT8TY&08ki?=(&K(q`;o1E9(@$8?8MjM3wA4!`JA`HFui@)T}@vGOEgGd0a^T z=%%H{EkeHy`hK-cHe2@m(+u#nmU!uW5&9ZKY0?WkeE6`88V~i2tE1BfdnrNZQRuW# z{d;}Y2gxAPn5M?;e`XvsY`K~CsqooEX}jcaRUoTLuJ+BFI)8lAp1vcQR6srD&Z#6M zKldD#@kI0;{8z}}ABg2!KG*%r=^mwJEA!Mk0Zz}$B8_wkbM0#{n53TPw6spF*IAcP zg^TY$3J>c?#Zw_G^tu^s;D=hGZ9e41`vNDN`1G(*@kzCNt+WsP+=1sdcUUK8WK6BE z_koUxkGSAh()>W4;ZUgEOe;%IPX1VBGi1nB2eiQuq%bOX}_`0SU?MEvzzOdCRm`CqQq^6{WncoIsEA1}ytu%waZwT#XOlO^%iw@TH z&X>4k((6}Ao_4o`XSBazX13Cj29ilthLL`8c_Zp7M{QT)VOhb?JoXWAPjfHuG(=du z@hT)I(~LSJw2Da^pT|ImxO(iqm369UMXbc6)1}XQ#q7Ud$3r`2bdmwh4g_X4B3lcwMDZ5I6kVNF0e`wdKjN79{fWGG0>QL4HfGe;t5E!WYnR$F8CLeGMv| zp8-_wN*;cIcoqh@F+VTVgxmt*)*B-F7BWmVAp8}uz92hRz{YwJvmteiEB z1&Z9fqaq^O73<_(HZ@MCevQ>R{=$V5$I9VQOMJ=8lJ>+;H5%lEVQge80HnUGy=n^r z117f$j^w9cL1gX;sC>LKxxB^hZbN=H*cW0FCiKhE*RR+pC%I?PaQl%dwx||%}B9F3Q7%1Bz(?}H* zU4N(Li$^l|_??9g@#i=9^6H=q`r)Z2sMbfnjOvBec4Moni9EB)=VEco5!hwlZ?Q@Z zM6a4Lk$LaL4baYMtX8za!J&^o+PRjlX((BC_S&^4Fkokl<1(s+Z6=>JLc&c-OeiH5 z4#IsJ-%1TjCbY$iDiL;{9{|-3Equ_JB~eVcp`zloew$!>)P!8lRM%Y98n*25>z#ow zJ#<#~q+?PFE5kuWNgiErUZE4-Q_iGM4ipUxC94@) z<1IJP_aNCfj7@%-K#sXN0q@pxt#!AQ37ybLGi&Jw|-UV0M(ABZc)^#zQ7#7A>4 z0fcW2%xrd&?T>~g78Sy|+1*agT%ls-j4ixrb#kxR2%Lajx0I_rUssmo~gyK~1AaPBg}`v9q{<@;4E zG{4q9@wX#eU+%eD77HNoG_J7Ky6r}6;S2r1qFZ1pPtS_>jacF?X1MvVXHJ4Nj2Ltq zO+{-W`NGxd{Z+R?Qc{}GJt(FUCsbvicGu)#ze0uav(-=!DQLu=e2rlJL#h3eF4zsd z-o-lw+tFVH9BO*uj!J(z1YX#pkf81%sfgM@dVm|=qCGR@6&Yp9CeK#F($P-Vwac<0 z@a#w`-!Cr8?@x#lGs1%3B;c9)2_+C*hU@M>!iFBJoRbk+;io^EzeY8zx?kEBJ}aRW z;{SmXzz`;y9yru@a>E$=9W_Bj=JYANQ(}F;n_dfh=G!>%0;Uzh1VlqVIS3~NF+$l6#ka|I(iX%q98Y^Pb`ya9~^mYTYVk1fUuR_1Hb%Xt-+ z1T2ztlC8UiVx^PjyaLQdc=zQcEl5EZ{6BzXK$P*5W9}OI`N;5tC$X^IRi%FdgSFJS z1aVsaaL0Q$F&OH|q{B!X4?X;*Y9j7vC{9`$buu@@C>BFzn%HA#freIn+LU5tLj5m8SkxoS7rO8HqUrwq_JsU09qC?wkj_$kBbwO16l6JjxtC`B=02N*QdOY{ ziC3V}A3^h{CTMq0%6KSwZQK~<{8~dmI49P@-!Mo&68$Fnx|86tXCO!1~0U+bi!>z-Bd?N+%epIr-m0s6x>yr zc~H4zm2g0dmJ=md4yI0#09KtvHnH_`FqwVhfBozgP&LBcpvdFy@ z-qH_@U6pQk-={s9@U*GxXt8Oi0+EjlKnRB%yRvPV@FP~ge^XK1{`wdq$mJlq>Qo3V z7N{heHdjka3kLXBYQkP%@C1y#x>%Y!02GBvy z+w_Yuc;a5v4 zLoz0G?AFkZ&gG+MfuG%}9CxnTGHa(fG0@_Tb$R{!d*Zd>SMG+fFfHe{J!+xxeKLGB zo={f{kvL(w=LNpE<4$;dNc-c4Mywt^3&0#??|Da*HvAmxQ@9Lz$t-pr4utq4)%(xS z&Q5O~e-~uzq~X?cXo**;el{|IZfjBe4q-^Kg7;m&o#s;6?Rgv!eg~?#ufgudfB)v$ zTD~0s{3{2=U>#MbBd~U(opnDA zdo8fp*a28YYVBn3Is?Rhb~iFHCaK5qf@i+&z#aP|VYlOg$7Wy!Qc^&5+luxP@C!Z% zTmJcg?@#&RUpW}=K9qwu$f}kDYkVjy7R=ln0Ne+~pbT&u8NK=Y5+eNfNTMy_z`jhU zdjrg^?pTcr*D&a9cBYHNV0+eIDEl~C_(E3tEr_|LI`gp9d&uIT~ zVFRvlP6fK+OS|ZDI68nK_R2f^WJSp$be^!pI(_Qj?&67T8&{d zVL;@)d+7bj(;K7#_`djrf4(pO_NPzVzMtofwOpL_U}!@BgV^#q|J>gZ0%hmzlt^}0 zQs#dMZU1vEfB7kb0_2{xBycuKODHcn+tdz~9C1eTXWHuTOZ#*NtYz@L3glR&pP^3a9`3i%Wfzz6;|M_BonR*G#)Z_Nt319w(=S~JwIAytG+uiU;S^irt zHhozT>&}RcX60(=J15A0e)bn2V2u+#!e{=A?*N=wT?t(HP6sLV`+wi|&+q(WL7-cE zMr7{@{#(WH*T)9fgBL9mr|-YxA9w3#Z|`4WZAo$Nt+ZFo(69Ud`srW9`{RdA)`L@4 zM?4sp{LcjqOqKf99Ymv;qW5QhzWe{9js7@*Pr=~y1J-wS>;C5w1E=r5LlT`LX!*qN zKRJj0-SO*raQc(1!?{ua)#*=2o^@0bFO{PHVEsSe>{TkyR$A|hkv`<7VDyi~(xVGR zt!``3`G*esv55Zm#hgxlP~$hBa$oS@n}NUV{bdvowSV=)=Ue_OQD=$*ZADeuDgNJF h-W~rxF52+M7Th&>%)$&qW)t{x(cp@Hsh(rx{{X>Ic}D;M literal 0 HcmV?d00001 diff --git a/doc/ci/runners/img/specific_runner_ip_address.png b/doc/ci/runners/img/specific_runner_ip_address.png new file mode 100644 index 0000000000000000000000000000000000000000..3b4c3e9f2ebc30f18ebbd9fc3c34bac35a99907b GIT binary patch literal 42055 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

}D0T5F5YzH_;Me!m>(7W5A*w@8RwZ@#UV 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 +**Notes:** +- [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6. +- The `$CI_ENVIRONMENT_SLUG` was [introduced][ce-7983] in GitLab 8.15. +- The `name` and `url` parameters can use any of the defined CI variables, + including predefined, secure variables and `.gitlab-ci.yml` [`variables`](#variables). + You however cannot use variables defined under `script`. + +For example: + +```yaml +deploy as review app: + stage: deploy + script: make deploy + environment: + name: review/$CI_COMMIT_REF_NAME + url: https://$CI_ENVIRONMENT_SLUG.example.com/ +``` + +The `deploy as review app` job will be marked as deployment to dynamically +create the `review/$CI_COMMIT_REF_NAME` environment, where `$CI_COMMIT_REF_NAME` +is an [environment variable][variables] set by the Runner. The +`$CI_ENVIRONMENT_SLUG` variable is based on the environment name, but suitable +for inclusion in URLs. In this case, if the `deploy as review app` job was run +in a branch named `pow`, this environment would be accessible with an URL like +`https://review-pow.example.com/`. + +This of course implies that the underlying server which hosts the application +is properly configured. + +The common use case is to create dynamic environments for branches and use them +as Review Apps. You can see a simple example using Review Apps at +. + +## `cache` + +> +**Notes:** +- Introduced in GitLab Runner v0.7.0. +- `cache` can be set globally and per-job. +- From GitLab 9.0, caching is enabled and shared between pipelines and jobs + by default. +- From GitLab 9.2, caches are restored before [artifacts](#artifacts). + +TIP: **Learn more:** +Read how caching works and find out some good practices in the +[caching dependencies documentation](../caching/index.md). + +`cache` is used to specify a list of files and directories which should be +cached between jobs. You can only use paths that are within the project +workspace. + +If `cache` is defined outside the scope of jobs, it means it is set +globally and all jobs will use that definition. + +### `cache:paths` + +Use the `paths` directive to choose which files or directories will be cached. +Wildcards can be used as well. + +Cache all files in `binaries` that end in `.apk` and the `.config` file: + +```yaml +rspec: + script: test + cache: + paths: + - binaries/*.apk + - .config +``` + +Locally defined cache overrides globally defined options. The following `rspec` +job will cache only `binaries/`: + +```yaml +cache: + paths: + - my/files + +rspec: + script: test + cache: + paths: + - binaries/ +``` + +### `cache:key` + +> Introduced in GitLab Runner v1.0.0. + +Since the cache is shared between jobs, if you're using different +paths for different jobs, you should also set a different `cache:key` +otherwise cache content can be overwritten. + +The `key` directive allows you to define the affinity of caching between jobs, +allowing to have a single cache for all jobs, cache per-job, cache per-branch +or any other way that fits your workflow. This way, you can fine tune caching, +allowing you to cache data between different jobs or even different branches. + +The `cache:key` variable can use any of the +[predefined variables](../variables/README.md), and the default key, if not set, +is `$CI_JOB_NAME-$CI_COMMIT_REF_NAME` which translates as "per-job and +per-branch". It is the default across the project, therefore everything is +shared between pipelines and jobs running on the same branch by default. + +NOTE: **Note:** +The `cache:key` variable cannot contain the `/` character, or the equivalent +URI-encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden. + +For example, to enable per-branch caching: + +```yaml +cache: + key: "$CI_COMMIT_REF_SLUG" + paths: + - binaries/ +``` + +If you use **Windows Batch** to run your shell scripts you need to replace +`$` with `%`: + +```yaml +cache: + key: "%CI_JOB_STAGE%-%CI_COMMIT_REF_SLUG%" + paths: + - binaries/ +``` + +If you use **Windows PowerShell** to run your shell scripts you need to replace +`$` with `$env:`: + +```yaml +cache: + key: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_SLUG" + paths: + - binaries/ +``` + +### `cache:untracked` + +Set `untracked: true` to cache all files that are untracked in your Git +repository: + +```yaml +rspec: + script: test + cache: + untracked: true +``` + +Cache all Git untracked files and files in `binaries`: + +```yaml +rspec: + script: test + cache: + untracked: true + paths: + - binaries/ +>>>>>>> 03bbd847deb... Merge branch 'docs/ci-caching' into 'master' +``` + +In the above example we set up the `review_app` job to deploy to the `review` +environment, and we also defined a new `stop_review_app` job under `on_stop`. +Once the `review_app` job is successfully finished, it will trigger the +`stop_review_app` job based on what is defined under `when`. In this case we +set it up to `manual` so it will need a [manual action](#manual-actions) via +GitLab's web interface in order to run. + +The `stop_review_app` job is **required** to have the following keywords defined: + - `when` - [reference](#when) - `environment:name` - `environment:action` diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index cd889e7448..97715d0c46 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -10,7 +10,7 @@ should be deployed, upgraded, and configured. ## Chart Overview * **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). -* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components. +* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in alpha. Will support large deployments with horizontal scaling of individual GitLab components. * Other Charts * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. @@ -35,7 +35,7 @@ By offering individual containers and charts, we will be able to provide a numbe * Potential for rolling updates and canaries within a service, * and plenty more. -This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We are planning to launch this chart in beta by the end of 2017. +Presently this chart is available in alpha for testing, and not recommended for production use. Learn more about the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). diff --git a/doc/user/project/merge_requests/maintainer_access.md b/doc/user/project/merge_requests/maintainer_access.md index 7feccc28f6..c9763a3fe0 100644 --- a/doc/user/project/merge_requests/maintainer_access.md +++ b/doc/user/project/merge_requests/maintainer_access.md @@ -1,12 +1,17 @@ # Allow maintainer pushes for merge requests across forks +> [Introduced][ce-17395] in GitLab 10.6. + This feature is available for merge requests across forked projects that are -publicly accessible. It makes it easier for maintainers of projects to collaborate -on merge requests across forks. +publicly accessible. It makes it easier for maintainers of projects to +collaborate on merge requests across forks. -When enabling this feature for a merge request, you give can give members with push access to the target project rights to edit files on the source branch of the merge request. +When enabled for a merge request, members with merge access to the target +branch of the project will be granted write permissions to the source branch +of the merge request. -The feature can only be enabled by users who already have push access to the source project. And only lasts while the merge request is open. +The feature can only be enabled by users who already have push access to the +source project, and only lasts while the merge request is open. Enable this functionality while creating a merge request: diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index 75b64ae9af..ce401c1c31 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -21,12 +21,13 @@ module Banzai # # See http://en.wikipedia.org/wiki/URI_scheme # - # The negative lookbehind ensures that users can paste a URL followed by a - # period or comma for punctuation without those characters being included - # in the generated link. + # The negative lookbehind ensures that users can paste a URL followed by + # punctuation without those characters being included in the generated + # link. It matches the behaviour of Rinku 2.0.1: + # https://github.com/vmg/rinku/blob/v2.0.1/ext/rinku/autolink.c#L65 # - # Rubular: http://rubular.com/r/JzPhi6DCZp - LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?]+)(? 200.kilobytes - - text.force_encoding('UTF-8') - - raise UnsupportedEncoding unless text.valid_encoding? end def validate_delimiter!(condition) diff --git a/lib/gitlab/gitaly_client/conflict_files_stitcher.rb b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb index 97c13d1fdb..c275a065bc 100644 --- a/lib/gitlab/gitaly_client/conflict_files_stitcher.rb +++ b/lib/gitlab/gitaly_client/conflict_files_stitcher.rb @@ -17,7 +17,7 @@ module Gitlab current_file = file_from_gitaly_header(gitaly_file.header) else - current_file.content << gitaly_file.content + current_file.raw_content << gitaly_file.content end end end diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index b502daea41..cbb0089bde 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -122,14 +122,10 @@ describe Banzai::Filter::AutolinkFilter do end it 'does not include trailing punctuation' do - doc = filter("See #{link}.") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}, ok?") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}...") - expect(doc.at_css('a').text).to eq link + ['.', ', ok?', '...', '?', '!', ': is that ok?'].each do |trailing_punctuation| + doc = filter("See #{link}#{trailing_punctuation}") + expect(doc.at_css('a').text).to eq link + end end it 'includes trailing punctuation when part of a balanced pair' do diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index f969f9e8e3..18cef8ec99 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -315,13 +315,19 @@ describe Gitlab::Auth do it "tries to autheticate with db before ldap" do expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login) - gl_auth.find_with_user_password(username, password) + expect(gl_auth.find_with_user_password(username, password)).to eq(user) end - it "uses ldap as fallback to for authentication" do - expect(Gitlab::Auth::LDAP::Authentication).to receive(:login) + it "does not find user by using ldap as fallback to for authentication" do + expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil) - gl_auth.find_with_user_password('ldap_user', 'password') + expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil + end + + it "find new user by using ldap as fallback to for authentication" do + expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(user) + + expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user) end end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 448c6fb57d..3a9371ed2e 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -510,6 +510,28 @@ describe Gitlab::Ci::Trace do it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid end + + context 'when there is a validation error on Ci::Build' do + before do + allow_any_instance_of(Ci::Build).to receive(:save).and_return(false) + allow_any_instance_of(Ci::Build).to receive_message_chain(:errors, :full_messages) + .and_return(%w[Error Error]) + end + + context "when erase old trace with 'save'" do + before do + build.send(:write_attribute, :trace, nil) + build.save + end + + it 'old trace is not deleted' do + build.reload + expect(build.trace.raw).to eq(trace_content) + end + end + + it_behaves_like 'archive trace in database' + end end end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 83d431a745..e68c9850f6 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -161,6 +161,11 @@ describe Gitlab::EncodingHelper do 'removes invalid bytes from ASCII-8bit encoded multibyte string.', "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'), "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg" + ], + [ + 'handles UTF-16BE encoded strings', + "\xFE\xFF\x00\x41".force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM + "\xEF\xBB\xBFA" # An "A" prepended with UTF-8 BOM ] ].each do |description, test_string, xpect| it description do diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb new file mode 100644 index 0000000000..afed6c32af --- /dev/null +++ b/spec/lib/gitlab/git/conflict/file_spec.rb @@ -0,0 +1,50 @@ +# coding: utf-8 +require 'spec_helper' + +describe Gitlab::Git::Conflict::File do + let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } } + let(:invalid_content) { described_class.new(nil, nil, conflict, "a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } + let(:valid_content) { described_class.new(nil, nil, conflict, "Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + + describe '#lines' do + context 'when the content contains non-UTF-8 characters' do + it 'raises UnsupportedEncoding' do + expect { invalid_content.lines } + .to raise_error(described_class::UnsupportedEncoding) + end + end + + context 'when the content can be converted to UTF-8' do + it 'sets lines to the lines' do + expect(valid_content.lines).to eq([{ + full_line: 'España', + type: nil, + line_obj_index: 0, + line_old: 1, + line_new: 1 + }]) + end + + it 'sets the type to text' do + expect(valid_content.type).to eq('text') + end + end + end + + describe '#content' do + context 'when the content contains non-UTF-8 characters' do + it 'raises UnsupportedEncoding' do + expect { invalid_content.content } + .to raise_error(described_class::UnsupportedEncoding) + end + end + + context 'when the content can be converted to UTF-8' do + it 'returns a valid UTF-8 string' do + expect(valid_content.content).to eq('España') + expect(valid_content.content).to be_valid_encoding + expect(valid_content.content.encoding).to eq(Encoding::UTF_8) + end + end + end +end diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb index 7b035a381f..29a1702a1c 100644 --- a/spec/lib/gitlab/git/conflict/parser_spec.rb +++ b/spec/lib/gitlab/git/conflict/parser_spec.rb @@ -212,13 +212,6 @@ CONFLICT .not_to raise_error end end - - context 'when the file contains non-UTF-8 characters' do - it 'raises UnsupportedEncoding' do - expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } - .to raise_error(Gitlab::Git::Conflict::Parser::UnsupportedEncoding) - end - end end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 59b82bfa09..b40749298d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2050,6 +2050,35 @@ describe Ci::Build do subject.drop! end + + context 'when retry service raises Gitlab::Access::AccessDeniedError exception' do + let(:retry_service) { Ci::RetryBuildService.new(subject.project, subject.user) } + + before do + allow_any_instance_of(Ci::RetryBuildService) + .to receive(:execute) + .with(subject) + .and_raise(Gitlab::Access::AccessDeniedError) + allow(Rails.logger).to receive(:error) + end + + it 'handles raised exception' do + expect { subject.drop! }.not_to raise_exception(Gitlab::Access::AccessDeniedError) + end + + it 'logs the error' do + subject.drop! + + expect(Rails.logger) + .to have_received(:error) + .with(a_string_matching("Unable to auto-retry job #{subject.id}")) + end + + it 'fails the job' do + subject.drop! + expect(subject.failed?).to be_truthy + end + end end context 'when build is not configured to be retried' do diff --git a/vendor/assets/javascripts/xterm/xterm.js b/vendor/assets/javascripts/xterm/xterm.js new file mode 100644 index 0000000000..11ce3c73db --- /dev/null +++ b/vendor/assets/javascripts/xterm/xterm.js @@ -0,0 +1,2235 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (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 0) { + self.terminal.handler(diff); + } + } + }, 0); +}; + +/** + * Positions the composition view on top of the cursor and the textarea just below it (so the + * IME helper dialog is positioned correctly). + */ +CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { + if (!this.isComposing) { + return; + } + var cursor = this.terminal.element.querySelector('.terminal-cursor'); + if (cursor) { + // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within + // the .xterm element. + var xtermRows = this.terminal.element.querySelector('.xterm-rows'); + var cursorTop = xtermRows.offsetTop + cursor.offsetTop; + + this.compositionView.style.left = cursor.offsetLeft + 'px'; + this.compositionView.style.top = cursorTop + 'px'; + this.compositionView.style.height = cursor.offsetHeight + 'px'; + this.compositionView.style.lineHeight = cursor.offsetHeight + 'px'; + // Sync the textarea to the exact position of the composition view so the IME knows where the + // text is. + var compositionViewBounds = this.compositionView.getBoundingClientRect(); + this.textarea.style.left = cursor.offsetLeft + 'px'; + this.textarea.style.top = cursorTop + 'px'; + this.textarea.style.width = compositionViewBounds.width + 'px'; + this.textarea.style.height = compositionViewBounds.height + 'px'; + this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; + } + if (!dontRecurse) { + setTimeout(this.updateCompositionElements.bind(this, true), 0); + } +}; + +/** + * Clears the textarea's position so that the cursor does not blink on IE. + * @private + */ +CompositionHelper.prototype.clearTextareaPosition = function () { + this.textarea.style.left = ''; + this.textarea.style.top = ''; +}; + +exports.CompositionHelper = CompositionHelper; + +},{}],2:[function(_dereq_,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + */ + +function EventEmitter() { + this._events = this._events || {}; +} + +EventEmitter.prototype.addListener = function (type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.removeListener = function (type, listener) { + if (!this._events[type]) return; + + var obj = this._events[type], + i = obj.length; + + while (i--) { + if (obj[i] === listener || obj[i].listener === listener) { + obj.splice(i, 1); + return; + } + } +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = function (type) { + if (this._events[type]) delete this._events[type]; +}; + +EventEmitter.prototype.once = function (type, listener) { + var self = this; + function on() { + var args = Array.prototype.slice.call(arguments); + this.removeListener(type, on); + return listener.apply(this, args); + } + on.listener = listener; + return this.on(type, on); +}; + +EventEmitter.prototype.emit = function (type) { + if (!this._events[type]) return; + + var args = Array.prototype.slice.call(arguments, 1), + obj = this._events[type], + l = obj.length, + i = 0; + + for (; i < l; i++) { + obj[i].apply(this, args); + } +}; + +EventEmitter.prototype.listeners = function (type) { + return this._events[type] = this._events[type] || []; +}; + +exports.EventEmitter = EventEmitter; + +},{}],3:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + */ + +/** + * Represents the viewport of a terminal, the visible area within the larger buffer of output. + * Logic for the virtual scroll bar is included in this object. + * @param {Terminal} terminal The Terminal object. + * @param {HTMLElement} viewportElement The DOM element acting as the viewport + * @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of + * the terminal. + */ +function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) { + this.terminal = terminal; + this.viewportElement = viewportElement; + this.scrollArea = scrollArea; + this.charMeasureElement = charMeasureElement; + this.currentRowHeight = 0; + this.lastRecordedBufferLength = 0; + this.lastRecordedViewportHeight = 0; + + this.terminal.on('scroll', this.syncScrollArea.bind(this)); + this.terminal.on('resize', this.syncScrollArea.bind(this)); + this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + + this.syncScrollArea(); +} + +/** + * Refreshes row height, setting line-height, viewport height and scroll area height if + * necessary. + * @param {number|undefined} charSize A character size measurement bounding rect object, if it + * doesn't exist it will be created. + */ +Viewport.prototype.refresh = function (charSize) { + var size = charSize || this.charMeasureElement.getBoundingClientRect(); + if (size.height > 0) { + var rowHeightChanged = size.height !== this.currentRowHeight; + if (rowHeightChanged) { + this.currentRowHeight = size.height; + this.viewportElement.style.lineHeight = size.height + 'px'; + this.terminal.rowContainer.style.lineHeight = size.height + 'px'; + } + var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; + if (rowHeightChanged || viewportHeightChanged) { + this.lastRecordedViewportHeight = this.terminal.rows; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + } + this.scrollArea.style.height = size.height * this.lastRecordedBufferLength + 'px'; + } +}; + +/** + * Updates dimensions and synchronizes the scroll area if necessary. + */ +Viewport.prototype.syncScrollArea = function () { + if (this.lastRecordedBufferLength !== this.terminal.lines.length) { + // If buffer height changed + this.lastRecordedBufferLength = this.terminal.lines.length; + this.refresh(); + } else if (this.lastRecordedViewportHeight !== this.terminal.rows) { + // If viewport height changed + this.refresh(); + } else { + // If size has changed, refresh viewport + var size = this.charMeasureElement.getBoundingClientRect(); + if (size.height !== this.currentRowHeight) { + this.refresh(size); + } + } + + // Sync scrollTop + var scrollTop = this.terminal.ydisp * this.currentRowHeight; + if (this.viewportElement.scrollTop !== scrollTop) { + this.viewportElement.scrollTop = scrollTop; + } +}; + +/** + * Handles scroll events on the viewport, calculating the new viewport and requesting the + * terminal to scroll to it. + * @param {Event} ev The scroll event. + */ +Viewport.prototype.onScroll = function (ev) { + var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); + var diff = newRow - this.terminal.ydisp; + this.terminal.scrollDisp(diff, true); +}; + +/** + * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual + * scrolling to `onScroll`, this event needs to be attached manually by the consumer of + * `Viewport`. + * @param {WheelEvent} ev The mouse wheel event. + */ +Viewport.prototype.onWheel = function (ev) { + if (ev.deltaY === 0) { + // Do nothing if it's not a vertical scroll event + return; + } + // Fallback to WheelEvent.DOM_DELTA_PIXEL + var multiplier = 1; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + multiplier = this.currentRowHeight; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentRowHeight * this.terminal.rows; + } + this.viewportElement.scrollTop += ev.deltaY * multiplier; + // Prevent the page from scrolling when the terminal scrolls + ev.preventDefault(); +}; + +exports.Viewport = Viewport; + +},{}],4:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2016, SourceLair Private Company (MIT License) + */ + +/** + * Clipboard handler module. This module contains methods for handling all + * clipboard-related events appropriately in the terminal. + * @module xterm/handlers/Clipboard + */ + +/** + * Prepares text copied from terminal selection, to be saved in the clipboard by: + * 1. stripping all trailing white spaces + * 2. converting all non-breaking spaces to regular spaces + * @param {string} text The copied text that needs processing for storing in clipboard + * @returns {string} + */ +function prepareTextForClipboard(text) { + var space = String.fromCharCode(32), + nonBreakingSpace = String.fromCharCode(160), + allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), + processedText = text.split('\n').map(function (line) { + // Strip all trailing white spaces and convert all non-breaking spaces + // to regular spaces. + var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); + + return processedLine; + }).join('\n'); + + return processedText; +} + +/** + * Binds copy functionality to the given terminal. + * @param {ClipboardEvent} ev The original copy event to be handled + */ +function copyHandler(ev, term) { + var copiedText = window.getSelection().toString(), + text = prepareTextForClipboard(copiedText); + + if (term.browser.isMSIE) { + window.clipboardData.setData('Text', text); + } else { + ev.clipboardData.setData('text/plain', text); + } + + ev.preventDefault(); // Prevent or the original text will be copied. +} + +/** + * Redirect the clipboard's data to the terminal's input handler. + * @param {ClipboardEvent} ev The original paste event to be handled + * @param {Terminal} term The terminal on which to apply the handled paste event + */ +function pasteHandler(ev, term) { + ev.stopPropagation(); + + var dispatchPaste = function dispatchPaste(text) { + term.handler(text); + term.textarea.value = ''; + return term.cancel(ev); + }; + + if (term.browser.isMSIE) { + if (window.clipboardData) { + var text = window.clipboardData.getData('Text'); + dispatchPaste(text); + } + } else { + if (ev.clipboardData) { + var text = ev.clipboardData.getData('text/plain'); + dispatchPaste(text); + } + } +} + +/** + * Bind to right-click event and allow right-click copy and paste. + * + * **Logic** + * If text is selected and right-click happens on selected text, then + * do nothing to allow seamless copying. + * If no text is selected or right-click is outside of the selection + * area, then bring the terminal's input below the cursor, in order to + * trigger the event on the textarea and allow-right click paste, without + * caring about disappearing selection. + * @param {ClipboardEvent} ev The original paste event to be handled + * @param {Terminal} term The terminal on which to apply the handled paste event + */ +function rightClickHandler(ev, term) { + var s = document.getSelection(), + selectedText = prepareTextForClipboard(s.toString()), + clickIsOnSelection = false; + + if (s.rangeCount) { + var r = s.getRangeAt(0), + cr = r.getClientRects(), + x = ev.clientX, + y = ev.clientY, + i, + rect; + + for (i = 0; i < cr.length; i++) { + rect = cr[i]; + clickIsOnSelection = x > rect.left && x < rect.right && y > rect.top && y < rect.bottom; + + if (clickIsOnSelection) { + break; + } + } + // If we clicked on selection and selection is not a single space, + // then mark the right click as copy-only. We check for the single + // space selection, as this can happen when clicking on an   + // and there is not much pointing in copying a single space. + if (selectedText.match(/^\s$/) || !selectedText.length) { + clickIsOnSelection = false; + } + } + + // Bring textarea at the cursor position + if (!clickIsOnSelection) { + term.textarea.style.position = 'fixed'; + term.textarea.style.width = '20px'; + term.textarea.style.height = '20px'; + term.textarea.style.left = x - 10 + 'px'; + term.textarea.style.top = y - 10 + 'px'; + term.textarea.style.zIndex = 1000; + term.textarea.focus(); + + // Reset the terminal textarea's styling + setTimeout(function () { + term.textarea.style.position = null; + term.textarea.style.width = null; + term.textarea.style.height = null; + term.textarea.style.left = null; + term.textarea.style.top = null; + term.textarea.style.zIndex = null; + }, 4); + } +} + +exports.prepareTextForClipboard = prepareTextForClipboard; +exports.copyHandler = copyHandler; +exports.pasteHandler = pasteHandler; +exports.rightClickHandler = rightClickHandler; + +},{}],5:[function(_dereq_,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isMSWindows = exports.isIphone = exports.isIpad = exports.isMac = exports.isMSIE = exports.isFirefox = undefined; + +var _Generic = _dereq_('./Generic.js'); + +var isNode = typeof navigator == 'undefined' ? true : false; /** + * xterm.js: xterm, in the browser + * Copyright (c) 2016, SourceLair Private Company (MIT License) + */ + +/** + * Browser utilities module. This module contains attributes and methods to help with + * identifying the current browser and platform. + * @module xterm/utils/Browser + */ + +var userAgent = isNode ? 'node' : navigator.userAgent; +var platform = isNode ? 'node' : navigator.platform; + +var isFirefox = exports.isFirefox = !!~userAgent.indexOf('Firefox'); +var isMSIE = exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); + +// Find the users platform. We use this to interpret the meta key +// and ISO third level shifts. +// http://stackoverflow.com/q/19877924/577598 +var isMac = exports.isMac = (0, _Generic.contains)(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); +var isIpad = exports.isIpad = platform === 'iPad'; +var isIphone = exports.isIphone = platform === 'iPhone'; +var isMSWindows = exports.isMSWindows = (0, _Generic.contains)(['Windows', 'Win16', 'Win32', 'WinCE'], platform); + +},{"./Generic.js":6}],6:[function(_dereq_,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2016, SourceLair Private Company (MIT License) + */ + +/** + * Generic utilities module. This module contains generic methods that can be helpful at + * different parts of the code base. + * @module xterm/utils/Generic + */ + +/** + * Return if the given array contains the given element + * @param {Array} array The array to search for the given element. + * @param {Object} el The element to look for into the array + */ +var contains = exports.contains = function contains(arr, el) { + return arr.indexOf(el) >= 0; +}; + +},{}],7:[function(_dereq_,module,exports){ +'use strict';var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};/** + * xterm.js: xterm, in the browser + * Copyright (c) 2014-2014, SourceLair Private Company (MIT License) + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * + * 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. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */var _CompositionHelper=_dereq_('./CompositionHelper.js');var _EventEmitter=_dereq_('./EventEmitter.js');var _Viewport=_dereq_('./Viewport.js');var _Clipboard=_dereq_('./handlers/Clipboard.js');var _Browser=_dereq_('./utils/Browser');var Browser=_interopRequireWildcard(_Browser);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}/** + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */// Let it work inside Node.js for automated testing purposes. +var document=typeof window!='undefined'?window.document:null;/** + * States + */var normal=0,escaped=1,csi=2,osc=3,charset=4,dcs=5,ignore=6;/** + * Terminal + *//** + * Creates a new `Terminal` object. + * + * @param {object} options An object containing a set of options, the available options are: + * - `cursorBlink` (boolean): Whether the terminal cursor blinks + * - `cols` (number): The number of columns of the terminal (horizontal size) + * - `rows` (number): The number of rows of the terminal (vertical size) + * + * @public + * @class Xterm Xterm + * @alias module:xterm/src/xterm + */function Terminal(options){var self=this;if(!(this instanceof Terminal)){return new Terminal(arguments[0],arguments[1],arguments[2]);}self.browser=Browser;self.cancel=Terminal.cancel;_EventEmitter.EventEmitter.call(this);if(typeof options==='number'){options={cols:arguments[0],rows:arguments[1],handler:arguments[2]};}options=options||{};Object.keys(Terminal.defaults).forEach(function(key){if(options[key]==null){options[key]=Terminal.options[key];if(Terminal[key]!==Terminal.defaults[key]){options[key]=Terminal[key];}}self[key]=options[key];});if(options.colors.length===8){options.colors=options.colors.concat(Terminal._colors.slice(8));}else if(options.colors.length===16){options.colors=options.colors.concat(Terminal._colors.slice(16));}else if(options.colors.length===10){options.colors=options.colors.slice(0,-2).concat(Terminal._colors.slice(8,-2),options.colors.slice(-2));}else if(options.colors.length===18){options.colors=options.colors.concat(Terminal._colors.slice(16,-2),options.colors.slice(-2));}this.colors=options.colors;this.options=options;// this.context = options.context || window; +// this.document = options.document || document; +this.parent=options.body||options.parent||(document?document.getElementsByTagName('body')[0]:null);this.cols=options.cols||options.geometry[0];this.rows=options.rows||options.geometry[1];this.geometry=[this.cols,this.rows];if(options.handler){this.on('data',options.handler);}/** + * The scroll position of the y cursor, ie. ybase + y = the y position within the entire + * buffer + */this.ybase=0;/** + * The scroll position of the viewport + */this.ydisp=0;/** + * The cursor's x position after ybase + */this.x=0;/** + * The cursor's y position after ybase + */this.y=0;/** + * Used to debounce the refresh function + */this.isRefreshing=false;/** + * Whether there is a full terminal refresh queued + */this.cursorState=0;this.cursorHidden=false;this.convertEol;this.state=0;this.queue='';this.scrollTop=0;this.scrollBottom=this.rows-1;this.customKeydownHandler=null;// modes +this.applicationKeypad=false;this.applicationCursor=false;this.originMode=false;this.insertMode=false;this.wraparoundMode=true;// defaults: xterm - true, vt100 - false +this.normal=null;// charset +this.charset=null;this.gcharset=null;this.glevel=0;this.charsets=[null];// mouse properties +this.decLocator;this.x10Mouse;this.vt200Mouse;this.vt300Mouse;this.normalMouse;this.mouseEvents;this.sendFocus;this.utfMouse;this.sgrMouse;this.urxvtMouse;// misc +this.element;this.children;this.refreshStart;this.refreshEnd;this.savedX;this.savedY;this.savedCols;// stream +this.readable=true;this.writable=true;this.defAttr=0<<18|257<<9|256<<0;this.curAttr=this.defAttr;this.params=[];this.currentParam=0;this.prefix='';this.postfix='';// leftover surrogate high from previous write invocation +this.surrogate_high='';/** + * An array of all lines in the entire buffer, including the prompt. The lines are array of + * characters which are 2-length arrays where [0] is an attribute and [1] is the character. + */this.lines=[];var i=this.rows;while(i--){this.lines.push(this.blankLine());}this.tabs;this.setupStops();// Store if user went browsing history in scrollback +this.userScrolling=false;}inherits(Terminal,_EventEmitter.EventEmitter);/** + * back_color_erase feature for xterm. + */Terminal.prototype.eraseAttr=function(){// if (this.is('screen')) return this.defAttr; +return this.defAttr&~0x1ff|this.curAttr&0x1ff;};/** + * Colors + */// Colors 0-15 +Terminal.tangoColors=[// dark: +'#2e3436','#cc0000','#4e9a06','#c4a000','#3465a4','#75507b','#06989a','#d3d7cf',// bright: +'#555753','#ef2929','#8ae234','#fce94f','#729fcf','#ad7fa8','#34e2e2','#eeeeec'];// Colors 0-15 + 16-255 +// Much thanks to TooTallNate for writing this. +Terminal.colors=function(){var colors=Terminal.tangoColors.slice(),r=[0x00,0x5f,0x87,0xaf,0xd7,0xff],i;// 16-231 +i=0;for(;i<216;i++){out(r[i/36%6|0],r[i/6%6|0],r[i%6]);}// 232-255 (grey) +i=0;for(;i<24;i++){r=8+i*10;out(r,r,r);}function out(r,g,b){colors.push('#'+hex(r)+hex(g)+hex(b));}function hex(c){c=c.toString(16);return c.length<2?'0'+c:c;}return colors;}();Terminal._colors=Terminal.colors.slice();Terminal.vcolors=function(){var out=[],colors=Terminal.colors,i=0,color;for(;i<256;i++){color=parseInt(colors[i].substring(1),16);out.push([color>>16&0xff,color>>8&0xff,color&0xff]);}return out;}();/** + * Options + */Terminal.defaults={colors:Terminal.colors,theme:'default',convertEol:false,termName:'xterm',geometry:[80,24],cursorBlink:false,visualBell:false,popOnBell:false,scrollback:1000,screenKeys:false,debug:false,cancelEvents:false// programFeatures: false, +// focusKeys: false, +};Terminal.options={};Terminal.focus=null;each(keys(Terminal.defaults),function(key){Terminal[key]=Terminal.defaults[key];Terminal.options[key]=Terminal.defaults[key];});/** + * Focus the terminal. Delegates focus handling to the terminal's DOM element. + */Terminal.prototype.focus=function(){return this.textarea.focus();};/** + * Retrieves an option's value from the terminal. + * @param {string} key The option key. + */Terminal.prototype.getOption=function(key,value){if(!(key in Terminal.defaults)){throw new Error('No option with key "'+key+'"');}if(typeof this.options[key]!=='undefined'){return this.options[key];}return this[key];};/** + * Sets an option on the terminal. + * @param {string} key The option key. + * @param {string} value The option value. + */Terminal.prototype.setOption=function(key,value){if(!(key in Terminal.defaults)){throw new Error('No option with key "'+key+'"');}this[key]=value;this.options[key]=value;};/** + * Binds the desired focus behavior on a given terminal object. + * + * @static + */Terminal.bindFocus=function(term){on(term.textarea,'focus',function(ev){if(term.sendFocus){term.send('\x1b[I');}term.element.classList.add('focus');term.showCursor();Terminal.focus=term;term.emit('focus',{terminal:term});});};/** + * Blur the terminal. Delegates blur handling to the terminal's DOM element. + */Terminal.prototype.blur=function(){return this.textarea.blur();};/** + * Binds the desired blur behavior on a given terminal object. + * + * @static + */Terminal.bindBlur=function(term){on(term.textarea,'blur',function(ev){term.refresh(term.y,term.y);if(term.sendFocus){term.send('\x1b[O');}term.element.classList.remove('focus');Terminal.focus=null;term.emit('blur',{terminal:term});});};/** + * Initialize default behavior + */Terminal.prototype.initGlobal=function(){var term=this;Terminal.bindKeys(this);Terminal.bindFocus(this);Terminal.bindBlur(this);// Bind clipboard functionality +on(this.element,'copy',function(ev){_Clipboard.copyHandler.call(this,ev,term);});on(this.textarea,'paste',function(ev){_Clipboard.pasteHandler.call(this,ev,term);});function rightClickHandlerWrapper(ev){_Clipboard.rightClickHandler.call(this,ev,term);}if(term.browser.isFirefox){on(this.element,'mousedown',function(ev){if(ev.button==2){rightClickHandlerWrapper(ev);}});}else{on(this.element,'contextmenu',rightClickHandlerWrapper);}};/** + * Apply key handling to the terminal + */Terminal.bindKeys=function(term){on(term.element,'keydown',function(ev){if(document.activeElement!=this){return;}term.keyDown(ev);},true);on(term.element,'keypress',function(ev){if(document.activeElement!=this){return;}term.keyPress(ev);},true);on(term.element,'keyup',term.focus.bind(term));on(term.textarea,'keydown',function(ev){term.keyDown(ev);},true);on(term.textarea,'keypress',function(ev){term.keyPress(ev);// Truncate the textarea's value, since it is not needed +this.value='';},true);on(term.textarea,'compositionstart',term.compositionHelper.compositionstart.bind(term.compositionHelper));on(term.textarea,'compositionupdate',term.compositionHelper.compositionupdate.bind(term.compositionHelper));on(term.textarea,'compositionend',term.compositionHelper.compositionend.bind(term.compositionHelper));term.on('refresh',term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));};/** + * Insert the given row to the terminal or produce a new one + * if no row argument is passed. Return the inserted row. + * @param {HTMLElement} row (optional) The row to append to the terminal. + */Terminal.prototype.insertRow=function(row){if((typeof row==='undefined'?'undefined':_typeof(row))!='object'){row=document.createElement('div');}this.rowContainer.appendChild(row);this.children.push(row);return row;};/** + * Opens the terminal within an element. + * + * @param {HTMLElement} parent The element to create the terminal within. + */Terminal.prototype.open=function(parent){var self=this,i=0,div;this.parent=parent||this.parent;if(!this.parent){throw new Error('Terminal requires a parent element.');}// Grab global elements +this.context=this.parent.ownerDocument.defaultView;this.document=this.parent.ownerDocument;this.body=this.document.getElementsByTagName('body')[0];//Create main element container +this.element=this.document.createElement('div');this.element.classList.add('terminal');this.element.classList.add('xterm');this.element.classList.add('xterm-theme-'+this.theme);this.element.style.height;this.element.setAttribute('tabindex',0);this.viewportElement=document.createElement('div');this.viewportElement.classList.add('xterm-viewport');this.element.appendChild(this.viewportElement);this.viewportScrollArea=document.createElement('div');this.viewportScrollArea.classList.add('xterm-scroll-area');this.viewportElement.appendChild(this.viewportScrollArea);// Create the container that will hold the lines of the terminal and then +// produce the lines the lines. +this.rowContainer=document.createElement('div');this.rowContainer.classList.add('xterm-rows');this.element.appendChild(this.rowContainer);this.children=[];// Create the container that will hold helpers like the textarea for +// capturing DOM Events. Then produce the helpers. +this.helperContainer=document.createElement('div');this.helperContainer.classList.add('xterm-helpers');// TODO: This should probably be inserted once it's filled to prevent an additional layout +this.element.appendChild(this.helperContainer);this.textarea=document.createElement('textarea');this.textarea.classList.add('xterm-helper-textarea');this.textarea.setAttribute('autocorrect','off');this.textarea.setAttribute('autocapitalize','off');this.textarea.setAttribute('spellcheck','false');this.textarea.tabIndex=0;this.textarea.addEventListener('focus',function(){self.emit('focus',{terminal:self});});this.textarea.addEventListener('blur',function(){self.emit('blur',{terminal:self});});this.helperContainer.appendChild(this.textarea);this.compositionView=document.createElement('div');this.compositionView.classList.add('composition-view');this.compositionHelper=new _CompositionHelper.CompositionHelper(this.textarea,this.compositionView,this);this.helperContainer.appendChild(this.compositionView);this.charMeasureElement=document.createElement('div');this.charMeasureElement.classList.add('xterm-char-measure-element');this.charMeasureElement.innerHTML='W';this.helperContainer.appendChild(this.charMeasureElement);for(;i +function sendButton(ev){var button,pos;// get the xterm-style button +button=getButton(ev);// get mouse coordinates +pos=getCoords(ev);if(!pos)return;sendEvent(button,pos);switch(ev.overrideType||ev.type){case'mousedown':pressed=button;break;case'mouseup':// keep it at the left +// button, just in case. +pressed=32;break;case'wheel':// nothing. don't +// interfere with +// `pressed`. +break;}}// motion example of a left click: +// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< +function sendMove(ev){var button=pressed,pos;pos=getCoords(ev);if(!pos)return;// buttons marked as motions +// are incremented by 32 +button+=32;sendEvent(button,pos);}// encode button and +// position to characters +function encode(data,ch){if(!self.utfMouse){if(ch===255)return data.push(0);if(ch>127)ch=127;data.push(ch);}else{if(ch===2047)return data.push(0);if(ch<127){data.push(ch);}else{if(ch>2047)ch=2047;data.push(0xC0|ch>>6);data.push(0x80|ch&0x3F);}}}// send a mouse event: +// regular/utf8: ^[[M Cb Cx Cy +// urxvt: ^[[ Cb ; Cx ; Cy M +// sgr: ^[[ Cb ; Cx ; Cy M/m +// vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r +// locator: CSI P e ; P b ; P r ; P c ; P p & w +function sendEvent(button,pos){// self.emit('mouse', { +// x: pos.x - 32, +// y: pos.x - 32, +// button: button +// }); +if(self.vt300Mouse){// NOTE: Unstable. +// http://www.vt100.net/docs/vt3xx-gp/chapter15.html +button&=3;pos.x-=32;pos.y-=32;var data='\x1b[24';if(button===0)data+='1';else if(button===1)data+='3';else if(button===2)data+='5';else if(button===3)return;else data+='0';data+='~['+pos.x+','+pos.y+']\r';self.send(data);return;}if(self.decLocator){// NOTE: Unstable. +button&=3;pos.x-=32;pos.y-=32;if(button===0)button=2;else if(button===1)button=4;else if(button===2)button=6;else if(button===3)button=3;self.send('\x1b['+button+';'+(button===3?4:0)+';'+pos.y+';'+pos.x+';'+(pos.page||0)+'&w');return;}if(self.urxvtMouse){pos.x-=32;pos.y-=32;pos.x++;pos.y++;self.send('\x1b['+button+';'+pos.x+';'+pos.y+'M');return;}if(self.sgrMouse){pos.x-=32;pos.y-=32;self.send('\x1b[<'+((button&3)===3?button&~3:button)+';'+pos.x+';'+pos.y+((button&3)===3?'m':'M'));return;}var data=[];encode(data,button);encode(data,pos.x);encode(data,pos.y);self.send('\x1b[M'+String.fromCharCode.apply(String,data));}function getButton(ev){var button,shift,meta,ctrl,mod;// two low bits: +// 0 = left +// 1 = middle +// 2 = right +// 3 = release +// wheel up/down: +// 1, and 2 - with 64 added +switch(ev.overrideType||ev.type){case'mousedown':button=ev.button!=null?+ev.button:ev.which!=null?ev.which-1:null;if(self.browser.isMSIE){button=button===1?0:button===4?1:button;}break;case'mouseup':button=3;break;case'DOMMouseScroll':button=ev.detail<0?64:65;break;case'wheel':button=ev.wheelDeltaY>0?64:65;break;}// next three bits are the modifiers: +// 4 = shift, 8 = meta, 16 = control +shift=ev.shiftKey?4:0;meta=ev.metaKey?8:0;ctrl=ev.ctrlKey?16:0;mod=shift|meta|ctrl;// no mods +if(self.vt200Mouse){// ctrl only +mod&=ctrl;}else if(!self.normalMouse){mod=0;}// increment to SP +button=32+(mod<<2)+button;return button;}// mouse coordinates measured in cols/rows +function getCoords(ev){var x,y,w,h,el;// ignore browsers without pageX for now +if(ev.pageX==null)return;x=ev.pageX;y=ev.pageY;el=self.element;// should probably check offsetParent +// but this is more portable +while(el&&el!==self.document.documentElement){x-=el.offsetLeft;y-=el.offsetTop;el='offsetParent'in el?el.offsetParent:el.parentNode;}// convert to cols/rows +w=self.element.clientWidth;h=self.element.clientHeight;x=Math.ceil(x/w*self.cols);y=Math.ceil(y/h*self.rows);// be sure to avoid sending +// bad positions to the program +if(x<0)x=0;if(x>self.cols)x=self.cols;if(y<0)y=0;if(y>self.rows)y=self.rows;// xterm sends raw bytes and +// starts at 32 (SP) for each. +x+=32;y+=32;return{x:x,y:y,type:'wheel'};}on(el,'mousedown',function(ev){if(!self.mouseEvents)return;// send the button +sendButton(ev);// ensure focus +self.focus();// fix for odd bug +//if (self.vt200Mouse && !self.normalMouse) { +if(self.vt200Mouse){ev.overrideType='mouseup';sendButton(ev);return self.cancel(ev);}// bind events +if(self.normalMouse)on(self.document,'mousemove',sendMove);// x10 compatibility mode can't send button releases +if(!self.x10Mouse){on(self.document,'mouseup',function up(ev){sendButton(ev);if(self.normalMouse)off(self.document,'mousemove',sendMove);off(self.document,'mouseup',up);return self.cancel(ev);});}return self.cancel(ev);});//if (self.normalMouse) { +// on(self.document, 'mousemove', sendMove); +//} +on(el,'wheel',function(ev){if(!self.mouseEvents)return;if(self.x10Mouse||self.vt300Mouse||self.decLocator)return;sendButton(ev);return self.cancel(ev);});// allow wheel scrolling in +// the shell for example +on(el,'wheel',function(ev){if(self.mouseEvents)return;self.viewport.onWheel(ev);return self.cancel(ev);});};/** + * Destroys the terminal. + */Terminal.prototype.destroy=function(){this.readable=false;this.writable=false;this._events={};this.handler=function(){};this.write=function(){};if(this.element.parentNode){this.element.parentNode.removeChild(this.element);}//this.emit('close'); +};/** + * Flags used to render terminal text properly + */Terminal.flags={BOLD:1,UNDERLINE:2,BLINK:4,INVERSE:8,INVISIBLE:16};/** + * Refreshes (re-renders) terminal content within two rows (inclusive) + * + * Rendering Engine: + * + * In the screen buffer, each character is stored as a an array with a character + * and a 32-bit integer: + * - First value: a utf-16 character. + * - Second value: + * - Next 9 bits: background color (0-511). + * - Next 9 bits: foreground color (0-511). + * - Next 14 bits: a mask for misc. flags: + * - 1=bold + * - 2=underline + * - 4=blink + * - 8=inverse + * - 16=invisible + * + * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) + * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) + * @param {boolean} queue Whether the refresh should ran right now or be queued + */Terminal.prototype.refresh=function(start,end,queue){var self=this;// queue defaults to true +queue=typeof queue=='undefined'?true:queue;/** + * The refresh queue allows refresh to execute only approximately 30 times a second. For + * commands that pass a significant amount of output to the write function, this prevents the + * terminal from maxing out the CPU and making the UI unresponsive. While commands can still + * run beyond what they do on the terminal, it is far better with a debounce in place as + * every single terminal manipulation does not need to be constructed in the DOM. + * + * A side-effect of this is that it makes ^C to interrupt a process seem more responsive. + */if(queue){// If refresh should be queued, order the refresh and return. +if(this._refreshIsQueued){// If a refresh has already been queued, just order a full refresh next +this._fullRefreshNext=true;}else{setTimeout(function(){self.refresh(start,end,false);},34);this._refreshIsQueued=true;}return;}// If refresh should be run right now (not be queued), release the lock +this._refreshIsQueued=false;// If multiple refreshes were requested, make a full refresh. +if(this._fullRefreshNext){start=0;end=this.rows-1;this._fullRefreshNext=false;// reset lock +}var x,y,i,line,out,ch,ch_width,width,data,attr,bg,fg,flags,row,parent,focused=document.activeElement;// If this is a big refresh, remove the terminal rows from the DOM for faster calculations +if(end-start>=this.rows/2){parent=this.element.parentNode;if(parent){this.element.removeChild(this.rowContainer);}}width=this.cols;y=start;if(end>=this.rows.length){this.log('`end` is too large. Most likely a bad CSR.');end=this.rows.length-1;}for(;y<=end;y++){row=y+this.ydisp;line=this.lines[row];out='';if(this.y===y-(this.ybase-this.ydisp)&&this.cursorState&&!this.cursorHidden){x=this.x;}else{x=-1;}attr=this.defAttr;i=0;for(;i';}if(data!==this.defAttr){if(data===-1){out+='';}else{var classNames=[];bg=data&0x1ff;fg=data>>9&0x1ff;flags=data>>18;if(flags&Terminal.flags.BOLD){if(!Terminal.brokenBold){classNames.push('xterm-bold');}// See: XTerm*boldColors +if(fg<8)fg+=8;}if(flags&Terminal.flags.UNDERLINE){classNames.push('xterm-underline');}if(flags&Terminal.flags.BLINK){classNames.push('xterm-blink');}// If inverse flag is on, then swap the foreground and background variables. +if(flags&Terminal.flags.INVERSE){/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */bg=[fg,fg=bg][0];// Should inverse just be before the +// above boldColors effect instead? +if(flags&1&&fg<8)fg+=8;}if(flags&Terminal.flags.INVISIBLE){classNames.push('xterm-hidden');}/** + * Weird situation: Invert flag used black foreground and white background results + * in invalid background color, positioned at the 256 index of the 256 terminal + * color map. Pin the colors manually in such a case. + * + * Source: https://github.com/sourcelair/xterm.js/issues/57 + */if(flags&Terminal.flags.INVERSE){if(bg==257){bg=15;}if(fg==256){fg=0;}}if(bg<256){classNames.push('xterm-bg-color-'+bg);}if(fg<256){classNames.push('xterm-color-'+fg);}out+='':out+='>';break;default:if(ch<=' '){out+=' ';}else{out+=ch;}break;}attr=data;}if(attr!==this.defAttr){out+='';}this.children[y].innerHTML=out;}if(parent){this.element.appendChild(this.rowContainer);}this.emit('refresh',{element:this.element,start:start,end:end});};/** + * Display the cursor element + */Terminal.prototype.showCursor=function(){if(!this.cursorState){this.cursorState=1;this.refresh(this.y,this.y);}};/** + * Scroll the terminal + */Terminal.prototype.scroll=function(){var row;if(++this.ybase===this.scrollback){this.ybase=this.ybase/2|0;this.lines=this.lines.slice(-(this.ybase+this.rows)+1);}if(!this.userScrolling){this.ydisp=this.ybase;}// last line +row=this.ybase+this.rows-1;// subtract the bottom scroll region +row-=this.rows-1-this.scrollBottom;if(row===this.lines.length){// potential optimization: +// pushing is faster than splicing +// when they amount to the same +// behavior. +this.lines.push(this.blankLine());}else{// add our new line +this.lines.splice(row,0,this.blankLine());}if(this.scrollTop!==0){if(this.ybase!==0){this.ybase--;if(!this.userScrolling){this.ydisp=this.ybase;}}this.lines.splice(this.ybase+this.scrollTop,1);}// this.maxRange(); +this.updateRange(this.scrollTop);this.updateRange(this.scrollBottom);this.emit('scroll',this.ydisp);};/** + * Scroll the display of the terminal + * @param {number} disp The number of lines to scroll down (negatives scroll up). + * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used + * to avoid unwanted events being handled by the veiwport when the event was triggered from the + * viewport originally. + */Terminal.prototype.scrollDisp=function(disp,suppressScrollEvent){if(disp<0){this.userScrolling=true;}else if(disp+this.ydisp>=this.ybase){this.userScrolling=false;}this.ydisp+=disp;if(this.ydisp>this.ybase){this.ydisp=this.ybase;}else if(this.ydisp<0){this.ydisp=0;}if(!suppressScrollEvent){this.emit('scroll',this.ydisp);}this.refresh(0,this.rows-1);};/** + * Scroll the display of the terminal by a number of pages. + * @param {number} pageCount The number of pages to scroll (negative scrolls up). + */Terminal.prototype.scrollPages=function(pageCount){this.scrollDisp(pageCount*(this.rows-1));};/** + * Scrolls the display of the terminal to the top. + */Terminal.prototype.scrollToTop=function(){this.scrollDisp(-this.ydisp);};/** + * Scrolls the display of the terminal to the bottom. + */Terminal.prototype.scrollToBottom=function(){this.scrollDisp(this.ybase-this.ydisp);};/** + * Writes text to the terminal. + * @param {string} text The text to write to the terminal. + */Terminal.prototype.write=function(data){var l=data.length,i=0,j,cs,ch,code,low,ch_width,row;this.refreshStart=this.y;this.refreshEnd=this.y;// apply leftover surrogate high from last write +if(this.surrogate_high){data=this.surrogate_high+data;this.surrogate_high='';}for(;i maybe move to default +code=data.charCodeAt(i);if(0xD800<=code&&code<=0xDBFF){// we got a surrogate high +// get surrogate low (next 2 bytes) +low=data.charCodeAt(i+1);if(isNaN(low)){// end of data stream, save surrogate high +this.surrogate_high=ch;continue;}code=(code-0xD800)*0x400+(low-0xDC00)+0x10000;ch+=data.charAt(i+1);}// surrogate low - already handled above +if(0xDC00<=code&&code<=0xDFFF)continue;switch(this.state){case normal:switch(ch){case'\x07':this.bell();break;// '\n', '\v', '\f' +case'\n':case'\x0b':case'\x0c':if(this.convertEol){this.x=0;}this.y++;if(this.y>this.scrollBottom){this.y--;this.scroll();}break;// '\r' +case'\r':this.x=0;break;// '\b' +case'\x08':if(this.x>0){this.x--;}break;// '\t' +case'\t':this.x=this.nextStop();break;// shift out +case'\x0e':this.setgLevel(1);break;// shift in +case'\x0f':this.setgLevel(0);break;// '\e' +case'\x1b':this.state=escaped;break;default:// ' ' +// calculate print space +// expensive call, therefore we save width in line buffer +ch_width=wcwidth(code);if(ch>=' '){if(this.charset&&this.charset[ch]){ch=this.charset[ch];}row=this.y+this.ybase;// insert combining char in last cell +// FIXME: needs handling after cursor jumps +if(!ch_width&&this.x){// dont overflow left +if(this.lines[row][this.x-1]){if(!this.lines[row][this.x-1][2]){// found empty cell after fullwidth, need to go 2 cells back +if(this.lines[row][this.x-2])this.lines[row][this.x-2][1]+=ch;}else{this.lines[row][this.x-1][1]+=ch;}this.updateRange(this.y);}break;}// goto next line if ch would overflow +// TODO: needs a global min terminal width of 2 +if(this.x+ch_width-1>=this.cols){// autowrap - DECAWM +if(this.wraparoundMode){this.x=0;this.y++;if(this.y>this.scrollBottom){this.y--;this.scroll();}}else{this.x=this.cols-1;if(ch_width===2)// FIXME: check for xterm behavior +continue;}}row=this.y+this.ybase;// insert mode: move characters to right +if(this.insertMode){// do this twice for a fullwidth char +for(var moves=0;moves Normal Keypad (DECKPNM). +case'>':this.log('Switching back to normal keypad.');this.applicationKeypad=false;this.viewport.syncScrollArea();this.state=normal;break;default:this.state=normal;this.error('Unknown ESC control: %s.',ch);break;}break;case charset:switch(ch){case'0':// DEC Special Character and Line Drawing Set. +cs=Terminal.charsets.SCLD;break;case'A':// UK +cs=Terminal.charsets.UK;break;case'B':// United States (USASCII). +cs=Terminal.charsets.US;break;case'4':// Dutch +cs=Terminal.charsets.Dutch;break;case'C':// Finnish +case'5':cs=Terminal.charsets.Finnish;break;case'R':// French +cs=Terminal.charsets.French;break;case'Q':// FrenchCanadian +cs=Terminal.charsets.FrenchCanadian;break;case'K':// German +cs=Terminal.charsets.German;break;case'Y':// Italian +cs=Terminal.charsets.Italian;break;case'E':// NorwegianDanish +case'6':cs=Terminal.charsets.NorwegianDanish;break;case'Z':// Spanish +cs=Terminal.charsets.Spanish;break;case'H':// Swedish +case'7':cs=Terminal.charsets.Swedish;break;case'=':// Swiss +cs=Terminal.charsets.Swiss;break;case'/':// ISOLatin (actually /A) +cs=Terminal.charsets.ISOLatin;i++;break;default:// Default +cs=Terminal.charsets.US;break;}this.setgCharset(this.gcharset,cs);this.gcharset=null;this.state=normal;break;case osc:// OSC Ps ; Pt ST +// OSC Ps ; Pt BEL +// Set Text Parameters. +if(ch==='\x1b'||ch==='\x07'){if(ch==='\x1b')i++;this.params.push(this.currentParam);switch(this.params[0]){case 0:case 1:case 2:if(this.params[1]){this.title=this.params[1];this.handleTitle(this.title);}break;case 3:// set X property +break;case 4:case 5:// change dynamic colors +break;case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:// change dynamic ui colors +break;case 46:// change log file +break;case 50:// dynamic font +break;case 51:// emacs shell +break;case 52:// manipulate selection data +break;case 104:case 105:case 110:case 111:case 112:case 113:case 114:case 115:case 116:case 117:case 118:// reset colors +break;}this.params=[];this.currentParam=0;this.state=normal;}else{if(!this.params.length){if(ch>='0'&&ch<='9'){this.currentParam=this.currentParam*10+ch.charCodeAt(0)-48;}else if(ch===';'){this.params.push(this.currentParam);this.currentParam='';}}else{this.currentParam+=ch;}}break;case csi:// '?', '>', '!' +if(ch==='?'||ch==='>'||ch==='!'){this.prefix=ch;break;}// 0 - 9 +if(ch>='0'&&ch<='9'){this.currentParam=this.currentParam*10+ch.charCodeAt(0)-48;break;}// '$', '"', ' ', '\'' +if(ch==='$'||ch==='"'||ch===' '||ch==='\''){this.postfix=ch;break;}this.params.push(this.currentParam);this.currentParam=0;// ';' +if(ch===';')break;this.state=normal;switch(ch){// CSI Ps A +// Cursor Up Ps Times (default = 1) (CUU). +case'A':this.cursorUp(this.params);break;// CSI Ps B +// Cursor Down Ps Times (default = 1) (CUD). +case'B':this.cursorDown(this.params);break;// CSI Ps C +// Cursor Forward Ps Times (default = 1) (CUF). +case'C':this.cursorForward(this.params);break;// CSI Ps D +// Cursor Backward Ps Times (default = 1) (CUB). +case'D':this.cursorBackward(this.params);break;// CSI Ps ; Ps H +// Cursor Position [row;column] (default = [1,1]) (CUP). +case'H':this.cursorPos(this.params);break;// CSI Ps J Erase in Display (ED). +case'J':this.eraseInDisplay(this.params);break;// CSI Ps K Erase in Line (EL). +case'K':this.eraseInLine(this.params);break;// CSI Pm m Character Attributes (SGR). +case'm':if(!this.prefix){this.charAttributes(this.params);}break;// CSI Ps n Device Status Report (DSR). +case'n':if(!this.prefix){this.deviceStatus(this.params);}break;/** + * Additions + */// CSI Ps @ +// Insert Ps (Blank) Character(s) (default = 1) (ICH). +case'@':this.insertChars(this.params);break;// CSI Ps E +// Cursor Next Line Ps Times (default = 1) (CNL). +case'E':this.cursorNextLine(this.params);break;// CSI Ps F +// Cursor Preceding Line Ps Times (default = 1) (CNL). +case'F':this.cursorPrecedingLine(this.params);break;// CSI Ps G +// Cursor Character Absolute [column] (default = [row,1]) (CHA). +case'G':this.cursorCharAbsolute(this.params);break;// CSI Ps L +// Insert Ps Line(s) (default = 1) (IL). +case'L':this.insertLines(this.params);break;// CSI Ps M +// Delete Ps Line(s) (default = 1) (DL). +case'M':this.deleteLines(this.params);break;// CSI Ps P +// Delete Ps Character(s) (default = 1) (DCH). +case'P':this.deleteChars(this.params);break;// CSI Ps X +// Erase Ps Character(s) (default = 1) (ECH). +case'X':this.eraseChars(this.params);break;// CSI Pm ` Character Position Absolute +// [column] (default = [row,1]) (HPA). +case'`':this.charPosAbsolute(this.params);break;// 141 61 a * HPR - +// Horizontal Position Relative +case'a':this.HPositionRelative(this.params);break;// CSI P s c +// Send Device Attributes (Primary DA). +// CSI > P s c +// Send Device Attributes (Secondary DA) +case'c':this.sendDeviceAttributes(this.params);break;// CSI Pm d +// Line Position Absolute [row] (default = [1,column]) (VPA). +case'd':this.linePosAbsolute(this.params);break;// 145 65 e * VPR - Vertical Position Relative +case'e':this.VPositionRelative(this.params);break;// CSI Ps ; Ps f +// Horizontal and Vertical Position [row;column] (default = +// [1,1]) (HVP). +case'f':this.HVPosition(this.params);break;// CSI Pm h Set Mode (SM). +// CSI ? Pm h - mouse escape codes, cursor escape codes +case'h':this.setMode(this.params);break;// CSI Pm l Reset Mode (RM). +// CSI ? Pm l +case'l':this.resetMode(this.params);break;// CSI Ps ; Ps r +// Set Scrolling Region [top;bottom] (default = full size of win- +// dow) (DECSTBM). +// CSI ? Pm r +case'r':this.setScrollRegion(this.params);break;// CSI s +// Save cursor (ANSI.SYS). +case's':this.saveCursor(this.params);break;// CSI u +// Restore cursor (ANSI.SYS). +case'u':this.restoreCursor(this.params);break;/** + * Lesser Used + */// CSI Ps I +// Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). +case'I':this.cursorForwardTab(this.params);break;// CSI Ps S Scroll up Ps lines (default = 1) (SU). +case'S':this.scrollUp(this.params);break;// CSI Ps T Scroll down Ps lines (default = 1) (SD). +// CSI Ps ; Ps ; Ps ; Ps ; Ps T +// CSI > Ps; Ps T +case'T':// if (this.prefix === '>') { +// this.resetTitleModes(this.params); +// break; +// } +// if (this.params.length > 2) { +// this.initMouseTracking(this.params); +// break; +// } +if(this.params.length<2&&!this.prefix){this.scrollDown(this.params);}break;// CSI Ps Z +// Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). +case'Z':this.cursorBackwardTab(this.params);break;// CSI Ps b Repeat the preceding graphic character Ps times (REP). +case'b':this.repeatPrecedingCharacter(this.params);break;// CSI Ps g Tab Clear (TBC). +case'g':this.tabClear(this.params);break;// CSI Pm i Media Copy (MC). +// CSI ? Pm i +// case 'i': +// this.mediaCopy(this.params); +// break; +// CSI Pm m Character Attributes (SGR). +// CSI > Ps; Ps m +// case 'm': // duplicate +// if (this.prefix === '>') { +// this.setResources(this.params); +// } else { +// this.charAttributes(this.params); +// } +// break; +// CSI Ps n Device Status Report (DSR). +// CSI > Ps n +// case 'n': // duplicate +// if (this.prefix === '>') { +// this.disableModifiers(this.params); +// } else { +// this.deviceStatus(this.params); +// } +// break; +// CSI > Ps p Set pointer mode. +// CSI ! p Soft terminal reset (DECSTR). +// CSI Ps$ p +// Request ANSI mode (DECRQM). +// CSI ? Ps$ p +// Request DEC private mode (DECRQM). +// CSI Ps ; Ps " p +case'p':switch(this.prefix){// case '>': +// this.setPointerMode(this.params); +// break; +case'!':this.softReset(this.params);break;// case '?': +// if (this.postfix === '$') { +// this.requestPrivateMode(this.params); +// } +// break; +// default: +// if (this.postfix === '"') { +// this.setConformanceLevel(this.params); +// } else if (this.postfix === '$') { +// this.requestAnsiMode(this.params); +// } +// break; +}break;// CSI Ps q Load LEDs (DECLL). +// CSI Ps SP q +// CSI Ps " q +// case 'q': +// if (this.postfix === ' ') { +// this.setCursorStyle(this.params); +// break; +// } +// if (this.postfix === '"') { +// this.setCharProtectionAttr(this.params); +// break; +// } +// this.loadLEDs(this.params); +// break; +// CSI Ps ; Ps r +// Set Scrolling Region [top;bottom] (default = full size of win- +// dow) (DECSTBM). +// CSI ? Pm r +// CSI Pt; Pl; Pb; Pr; Ps$ r +// case 'r': // duplicate +// if (this.prefix === '?') { +// this.restorePrivateValues(this.params); +// } else if (this.postfix === '$') { +// this.setAttrInRectangle(this.params); +// } else { +// this.setScrollRegion(this.params); +// } +// break; +// CSI s Save cursor (ANSI.SYS). +// CSI ? Pm s +// case 's': // duplicate +// if (this.prefix === '?') { +// this.savePrivateValues(this.params); +// } else { +// this.saveCursor(this.params); +// } +// break; +// CSI Ps ; Ps ; Ps t +// CSI Pt; Pl; Pb; Pr; Ps$ t +// CSI > Ps; Ps t +// CSI Ps SP t +// case 't': +// if (this.postfix === '$') { +// this.reverseAttrInRectangle(this.params); +// } else if (this.postfix === ' ') { +// this.setWarningBellVolume(this.params); +// } else { +// if (this.prefix === '>') { +// this.setTitleModeFeature(this.params); +// } else { +// this.manipulateWindow(this.params); +// } +// } +// break; +// CSI u Restore cursor (ANSI.SYS). +// CSI Ps SP u +// case 'u': // duplicate +// if (this.postfix === ' ') { +// this.setMarginBellVolume(this.params); +// } else { +// this.restoreCursor(this.params); +// } +// break; +// CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v +// case 'v': +// if (this.postfix === '$') { +// this.copyRectagle(this.params); +// } +// break; +// CSI Pt ; Pl ; Pb ; Pr ' w +// case 'w': +// if (this.postfix === '\'') { +// this.enableFilterRectangle(this.params); +// } +// break; +// CSI Ps x Request Terminal Parameters (DECREQTPARM). +// CSI Ps x Select Attribute Change Extent (DECSACE). +// CSI Pc; Pt; Pl; Pb; Pr$ x +// case 'x': +// if (this.postfix === '$') { +// this.fillRectangle(this.params); +// } else { +// this.requestParameters(this.params); +// //this.__(this.params); +// } +// break; +// CSI Ps ; Pu ' z +// CSI Pt; Pl; Pb; Pr$ z +// case 'z': +// if (this.postfix === '\'') { +// this.enableLocatorReporting(this.params); +// } else if (this.postfix === '$') { +// this.eraseRectangle(this.params); +// } +// break; +// CSI Pm ' { +// CSI Pt; Pl; Pb; Pr$ { +// case '{': +// if (this.postfix === '\'') { +// this.setLocatorEvents(this.params); +// } else if (this.postfix === '$') { +// this.selectiveEraseRectangle(this.params); +// } +// break; +// CSI Ps ' | +// case '|': +// if (this.postfix === '\'') { +// this.requestLocatorPosition(this.params); +// } +// break; +// CSI P m SP } +// Insert P s Column(s) (default = 1) (DECIC), VT420 and up. +// case '}': +// if (this.postfix === ' ') { +// this.insertColumns(this.params); +// } +// break; +// CSI P m SP ~ +// Delete P s Column(s) (default = 1) (DECDC), VT420 and up +// case '~': +// if (this.postfix === ' ') { +// this.deleteColumns(this.params); +// } +// break; +default:this.error('Unknown CSI code: %s.',ch);break;}this.prefix='';this.postfix='';break;case dcs:if(ch==='\x1b'||ch==='\x07'){if(ch==='\x1b')i++;switch(this.prefix){// User-Defined Keys (DECUDK). +case'':break;// Request Status String (DECRQSS). +// test: echo -e '\eP$q"p\e\\' +case'$q':var pt=this.currentParam,valid=false;switch(pt){// DECSCA +case'"q':pt='0"q';break;// DECSCL +case'"p':pt='61"p';break;// DECSTBM +case'r':pt=''+(this.scrollTop+1)+';'+(this.scrollBottom+1)+'r';break;// SGR +case'm':pt='0m';break;default:this.error('Unknown DCS Pt: %s.',pt);pt='';break;}this.send('\x1bP'+ +valid+'$r'+pt+'\x1b\\');break;// Set Termcap/Terminfo Data (xterm, experimental). +case'+p':break;// Request Termcap/Terminfo String (xterm, experimental) +// Regular xterm does not even respond to this sequence. +// This can cause a small glitch in vim. +// test: echo -ne '\eP+q6b64\e\\' +case'+q':var pt=this.currentParam,valid=false;this.send('\x1bP'+ +valid+'+r'+pt+'\x1b\\');break;default:this.error('Unknown DCS prefix: %s.',this.prefix);break;}this.currentParam=0;this.prefix='';this.state=normal;}else if(!this.currentParam){if(!this.prefix&&ch!=='$'&&ch!=='+'){this.currentParam=ch;}else if(this.prefix.length===2){this.currentParam=ch;}else{this.prefix+=ch;}}else{this.currentParam+=ch;}break;case ignore:// For PM and APC. +if(ch==='\x1b'||ch==='\x07'){if(ch==='\x1b')i++;this.state=normal;}break;}}this.updateRange(this.y);this.refresh(this.refreshStart,this.refreshEnd);};/** + * Writes text to the terminal, followed by a break line character (\n). + * @param {string} text The text to write to the terminal. + */Terminal.prototype.writeln=function(data){this.write(data+'\r\n');};/** + * Attaches a custom keydown handler which is run before keys are processed, giving consumers of + * xterm.js ultimate control as to what keys should be processed by the terminal and what keys + * should not. + * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a + * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent + * the default action. The function returns whether the event should be processed by xterm.js. + */Terminal.prototype.attachCustomKeydownHandler=function(customKeydownHandler){this.customKeydownHandler=customKeydownHandler;};/** + * Handle a keydown event + * Key Resources: + * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent + * @param {KeyboardEvent} ev The keydown event to be handled. + */Terminal.prototype.keyDown=function(ev){// Scroll down to prompt, whenever the user presses a key. +if(this.ybase!==this.ydisp){this.scrollToBottom();}if(this.customKeydownHandler&&this.customKeydownHandler(ev)===false){return false;}if(!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)){return false;}var self=this;var result=this.evaluateKeyEscapeSequence(ev);if(result.scrollDisp){this.scrollDisp(result.scrollDisp);return this.cancel(ev,true);}if(isThirdLevelShift(this,ev)){return true;}if(result.cancel){// The event is canceled at the end already, is this necessary? +this.cancel(ev,true);}if(!result.key){return true;}this.emit('keydown',ev);this.emit('key',result.key,ev);this.showCursor();this.handler(result.key);return this.cancel(ev,true);};/** + * Returns an object that determines how a KeyboardEvent should be handled. The key of the + * returned value is the new key code to pass to the PTY. + * + * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. + */Terminal.prototype.evaluateKeyEscapeSequence=function(ev){var result={// Whether to cancel event propogation (NOTE: this may not be needed since the event is +// canceled at the end of keyDown +cancel:false,// The new key even to emit +key:undefined,// The number of characters to scroll, if this is defined it will cancel the event +scrollDisp:undefined};var modifiers=ev.shiftKey<<0|ev.altKey<<1|ev.ctrlKey<<2|ev.metaKey<<3;switch(ev.keyCode){case 8:// backspace +if(ev.shiftKey){result.key='\x08';// ^H +break;}result.key='\x7f';// ^? +break;case 9:// tab +if(ev.shiftKey){result.key='\x1b[Z';break;}result.key='\t';result.cancel=true;break;case 13:// return/enter +result.key='\r';result.cancel=true;break;case 27:// escape +result.key='\x1b';result.cancel=true;break;case 37:// left-arrow +if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'D';// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards +// http://unix.stackexchange.com/a/108106 +if(result.key=='\x1b[1;3D'){result.key='\x1b[1;5D';}}else if(this.applicationCursor){result.key='\x1bOD';}else{result.key='\x1b[D';}break;case 39:// right-arrow +if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'C';// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward +// http://unix.stackexchange.com/a/108106 +if(result.key=='\x1b[1;3C'){result.key='\x1b[1;5C';}}else if(this.applicationCursor){result.key='\x1bOC';}else{result.key='\x1b[C';}break;case 38:// up-arrow +if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'A';// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow +// http://unix.stackexchange.com/a/108106 +if(result.key=='\x1b[1;3A'){result.key='\x1b[1;5A';}}else if(this.applicationCursor){result.key='\x1bOA';}else{result.key='\x1b[A';}break;case 40:// down-arrow +if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'B';// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow +// http://unix.stackexchange.com/a/108106 +if(result.key=='\x1b[1;3B'){result.key='\x1b[1;5B';}}else if(this.applicationCursor){result.key='\x1bOB';}else{result.key='\x1b[B';}break;case 45:// insert +if(!ev.shiftKey&&!ev.ctrlKey){// or + are used to +// copy-paste on some systems. +result.key='\x1b[2~';}break;case 46:// delete +if(modifiers){result.key='\x1b[3;'+(modifiers+1)+'~';}else{result.key='\x1b[3~';}break;case 36:// home +if(modifiers)result.key='\x1b[1;'+(modifiers+1)+'H';else if(this.applicationCursor)result.key='\x1bOH';else result.key='\x1b[H';break;case 35:// end +if(modifiers)result.key='\x1b[1;'+(modifiers+1)+'F';else if(this.applicationCursor)result.key='\x1bOF';else result.key='\x1b[F';break;case 33:// page up +if(ev.shiftKey){result.scrollDisp=-(this.rows-1);}else{result.key='\x1b[5~';}break;case 34:// page down +if(ev.shiftKey){result.scrollDisp=this.rows-1;}else{result.key='\x1b[6~';}break;case 112:// F1-F12 +if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'P';}else{result.key='\x1bOP';}break;case 113:if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'Q';}else{result.key='\x1bOQ';}break;case 114:if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'R';}else{result.key='\x1bOR';}break;case 115:if(modifiers){result.key='\x1b[1;'+(modifiers+1)+'S';}else{result.key='\x1bOS';}break;case 116:if(modifiers){result.key='\x1b[15;'+(modifiers+1)+'~';}else{result.key='\x1b[15~';}break;case 117:if(modifiers){result.key='\x1b[17;'+(modifiers+1)+'~';}else{result.key='\x1b[17~';}break;case 118:if(modifiers){result.key='\x1b[18;'+(modifiers+1)+'~';}else{result.key='\x1b[18~';}break;case 119:if(modifiers){result.key='\x1b[19;'+(modifiers+1)+'~';}else{result.key='\x1b[19~';}break;case 120:if(modifiers){result.key='\x1b[20;'+(modifiers+1)+'~';}else{result.key='\x1b[20~';}break;case 121:if(modifiers){result.key='\x1b[21;'+(modifiers+1)+'~';}else{result.key='\x1b[21~';}break;case 122:if(modifiers){result.key='\x1b[23;'+(modifiers+1)+'~';}else{result.key='\x1b[23~';}break;case 123:if(modifiers){result.key='\x1b[24;'+(modifiers+1)+'~';}else{result.key='\x1b[24~';}break;default:// a-z and space +if(ev.ctrlKey&&!ev.shiftKey&&!ev.altKey&&!ev.metaKey){if(ev.keyCode>=65&&ev.keyCode<=90){result.key=String.fromCharCode(ev.keyCode-64);}else if(ev.keyCode===32){// NUL +result.key=String.fromCharCode(0);}else if(ev.keyCode>=51&&ev.keyCode<=55){// escape, file sep, group sep, record sep, unit sep +result.key=String.fromCharCode(ev.keyCode-51+27);}else if(ev.keyCode===56){// delete +result.key=String.fromCharCode(127);}else if(ev.keyCode===219){// ^[ - escape +result.key=String.fromCharCode(27);}else if(ev.keyCode===221){// ^] - group sep +result.key=String.fromCharCode(29);}}else if(!this.browser.isMac&&ev.altKey&&!ev.ctrlKey&&!ev.metaKey){// On Mac this is a third level shift. Use instead. +if(ev.keyCode>=65&&ev.keyCode<=90){result.key='\x1b'+String.fromCharCode(ev.keyCode+32);}else if(ev.keyCode===192){result.key='\x1b`';}else if(ev.keyCode>=48&&ev.keyCode<=57){result.key='\x1b'+(ev.keyCode-48);}}break;}return result;};/** + * Set the G level of the terminal + * @param g + */Terminal.prototype.setgLevel=function(g){this.glevel=g;this.charset=this.charsets[g];};/** + * Set the charset for the given G level of the terminal + * @param g + * @param charset + */Terminal.prototype.setgCharset=function(g,charset){this.charsets[g]=charset;if(this.glevel===g){this.charset=charset;}};/** + * Handle a keypress event. + * Key Resources: + * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent + * @param {KeyboardEvent} ev The keypress event to be handled. + */Terminal.prototype.keyPress=function(ev){var key;this.cancel(ev);if(ev.charCode){key=ev.charCode;}else if(ev.which==null){key=ev.keyCode;}else if(ev.which!==0&&ev.charCode!==0){key=ev.which;}else{return false;}if(!key||(ev.altKey||ev.ctrlKey||ev.metaKey)&&!isThirdLevelShift(this,ev)){return false;}key=String.fromCharCode(key);this.emit('keypress',key,ev);this.emit('key',key,ev);this.showCursor();this.handler(key);return false;};/** + * Send data for handling to the terminal + * @param {string} data + */Terminal.prototype.send=function(data){var self=this;if(!this.queue){setTimeout(function(){self.handler(self.queue);self.queue='';},1);}this.queue+=data;};/** + * Ring the bell. + * Note: We could do sweet things with webaudio here + */Terminal.prototype.bell=function(){if(!this.visualBell)return;var self=this;this.element.style.borderColor='white';setTimeout(function(){self.element.style.borderColor='';},10);if(this.popOnBell)this.focus();};/** + * Log the current state to the console. + */Terminal.prototype.log=function(){if(!this.debug)return;if(!this.context.console||!this.context.console.log)return;var args=Array.prototype.slice.call(arguments);this.context.console.log.apply(this.context.console,args);};/** + * Log the current state as error to the console. + */Terminal.prototype.error=function(){if(!this.debug)return;if(!this.context.console||!this.context.console.error)return;var args=Array.prototype.slice.call(arguments);this.context.console.error.apply(this.context.console,args);};/** + * Resizes the terminal. + * + * @param {number} x The number of columns to resize to. + * @param {number} y The number of rows to resize to. + */Terminal.prototype.resize=function(x,y){var line,el,i,j,ch,addToY;if(x===this.cols&&y===this.rows){return;}if(x<1)x=1;if(y<1)y=1;// resize cols +j=this.cols;if(j x) +i=this.lines.length;while(i--){while(this.lines[i].length>x){this.lines[i].pop();}}}this.setupStops(j);this.cols=x;// resize rows +j=this.rows;addToY=0;if(j0&&this.lines.length<=this.ybase+this.y+addToY+1){// There is room above the buffer and there are no empty elements below the line, +// scroll up +this.ybase--;addToY++;if(this.ydisp>0){// Viewport is at the top of the buffer, must increase downwards +this.ydisp--;}}else{// Add a blank line if there is no buffer left at the top to scroll to, or if there +// are blank lines after the cursor +this.lines.push(this.blankLine());}}if(this.children.length y) +while(j-->y){if(this.lines.length>y+this.ybase){if(this.lines.length>this.ybase+this.y+1){// The line is a blank line below the cursor, remove it +this.lines.pop();}else{// The line is the cursor, scroll down +this.ybase++;this.ydisp++;}}if(this.children.length>y){el=this.children.shift();if(!el)continue;el.parentNode.removeChild(el);}}}this.rows=y;// Make sure that the cursor stays on screen +if(this.y>=y){this.y=y-1;}if(addToY){this.y+=addToY;}if(this.x>=x){this.x=x-1;}this.scrollTop=0;this.scrollBottom=y-1;this.refresh(0,this.rows-1);this.normal=null;this.geometry=[this.cols,this.rows];this.emit('resize',{terminal:this,cols:x,rows:y});};/** + * Updates the range of rows to refresh + * @param {number} y The number of rows to refresh next. + */Terminal.prototype.updateRange=function(y){if(ythis.refreshEnd)this.refreshEnd=y;// if (y > this.refreshEnd) { +// this.refreshEnd = y; +// if (y > this.rows - 1) { +// this.refreshEnd = this.rows - 1; +// } +// } +};/** + * Set the range of refreshing to the maximum value + */Terminal.prototype.maxRange=function(){this.refreshStart=0;this.refreshEnd=this.rows-1;};/** + * Setup the tab stops. + * @param {number} i + */Terminal.prototype.setupStops=function(i){if(i!=null){if(!this.tabs[i]){i=this.prevStop(i);}}else{this.tabs={};i=0;}for(;i0){}return x>=this.cols?this.cols-1:x<0?0:x;};/** + * Move the cursor one tab stop forward from the given position (default is current). + * @param {number} x The position to move the cursor one tab stop forward. + */Terminal.prototype.nextStop=function(x){if(x==null)x=this.x;while(!this.tabs[++x]&&x=this.cols?this.cols-1:x<0?0:x;};/** + * Erase in the identified line everything from "x" to the end of the line (right). + * @param {number} x The column from which to start erasing to the end of the line. + * @param {number} y The line in which to operate. + */Terminal.prototype.eraseRight=function(x,y){var line=this.lines[this.ybase+y],ch=[this.eraseAttr(),' ',1];// xterm +for(;xthis.scrollBottom){this.y--;this.scroll();}this.state=normal;};/** + * ESC M Reverse Index (RI is 0x8d). + */Terminal.prototype.reverseIndex=function(){var j;this.y--;if(this.y=this.rows){this.y=this.rows-1;}};/** + * CSI Ps C + * Cursor Forward Ps Times (default = 1) (CUF). + */Terminal.prototype.cursorForward=function(params){var param=params[0];if(param<1)param=1;this.x+=param;if(this.x>=this.cols){this.x=this.cols-1;}};/** + * CSI Ps D + * Cursor Backward Ps Times (default = 1) (CUB). + */Terminal.prototype.cursorBackward=function(params){var param=params[0];if(param<1)param=1;this.x-=param;if(this.x<0)this.x=0;};/** + * CSI Ps ; Ps H + * Cursor Position [row;column] (default = [1,1]) (CUP). + */Terminal.prototype.cursorPos=function(params){var row,col;row=params[0]-1;if(params.length>=2){col=params[1]-1;}else{col=0;}if(row<0){row=0;}else if(row>=this.rows){row=this.rows-1;}if(col<0){col=0;}else if(col>=this.cols){col=this.cols-1;}this.x=col;this.y=row;};/** + * CSI Ps J Erase in Display (ED). + * Ps = 0 -> Erase Below (default). + * Ps = 1 -> Erase Above. + * Ps = 2 -> Erase All. + * Ps = 3 -> Erase Saved Lines (xterm). + * CSI ? Ps J + * Erase in Display (DECSED). + * Ps = 0 -> Selective Erase Below (default). + * Ps = 1 -> Selective Erase Above. + * Ps = 2 -> Selective Erase All. + */Terminal.prototype.eraseInDisplay=function(params){var j;switch(params[0]){case 0:this.eraseRight(this.x,this.y);j=this.y+1;for(;j Erase to Right (default). + * Ps = 1 -> Erase to Left. + * Ps = 2 -> Erase All. + * CSI ? Ps K + * Erase in Line (DECSEL). + * Ps = 0 -> Selective Erase to Right (default). + * Ps = 1 -> Selective Erase to Left. + * Ps = 2 -> Selective Erase All. + */Terminal.prototype.eraseInLine=function(params){switch(params[0]){case 0:this.eraseRight(this.x,this.y);break;case 1:this.eraseLeft(this.x,this.y);break;case 2:this.eraseLine(this.y);break;}};/** + * CSI Pm m Character Attributes (SGR). + * Ps = 0 -> Normal (default). + * Ps = 1 -> Bold. + * Ps = 4 -> Underlined. + * Ps = 5 -> Blink (appears as Bold). + * Ps = 7 -> Inverse. + * Ps = 8 -> Invisible, i.e., hidden (VT300). + * Ps = 2 2 -> Normal (neither bold nor faint). + * Ps = 2 4 -> Not underlined. + * Ps = 2 5 -> Steady (not blinking). + * Ps = 2 7 -> Positive (not inverse). + * Ps = 2 8 -> Visible, i.e., not hidden (VT300). + * Ps = 3 0 -> Set foreground color to Black. + * Ps = 3 1 -> Set foreground color to Red. + * Ps = 3 2 -> Set foreground color to Green. + * Ps = 3 3 -> Set foreground color to Yellow. + * Ps = 3 4 -> Set foreground color to Blue. + * Ps = 3 5 -> Set foreground color to Magenta. + * Ps = 3 6 -> Set foreground color to Cyan. + * Ps = 3 7 -> Set foreground color to White. + * Ps = 3 9 -> Set foreground color to default (original). + * Ps = 4 0 -> Set background color to Black. + * Ps = 4 1 -> Set background color to Red. + * Ps = 4 2 -> Set background color to Green. + * Ps = 4 3 -> Set background color to Yellow. + * Ps = 4 4 -> Set background color to Blue. + * Ps = 4 5 -> Set background color to Magenta. + * Ps = 4 6 -> Set background color to Cyan. + * Ps = 4 7 -> Set background color to White. + * Ps = 4 9 -> Set background color to default (original). + * + * If 16-color support is compiled, the following apply. Assume + * that xterm's resources are set so that the ISO color codes are + * the first 8 of a set of 16. Then the aixterm colors are the + * bright versions of the ISO colors: + * Ps = 9 0 -> Set foreground color to Black. + * Ps = 9 1 -> Set foreground color to Red. + * Ps = 9 2 -> Set foreground color to Green. + * Ps = 9 3 -> Set foreground color to Yellow. + * Ps = 9 4 -> Set foreground color to Blue. + * Ps = 9 5 -> Set foreground color to Magenta. + * Ps = 9 6 -> Set foreground color to Cyan. + * Ps = 9 7 -> Set foreground color to White. + * Ps = 1 0 0 -> Set background color to Black. + * Ps = 1 0 1 -> Set background color to Red. + * Ps = 1 0 2 -> Set background color to Green. + * Ps = 1 0 3 -> Set background color to Yellow. + * Ps = 1 0 4 -> Set background color to Blue. + * Ps = 1 0 5 -> Set background color to Magenta. + * Ps = 1 0 6 -> Set background color to Cyan. + * Ps = 1 0 7 -> Set background color to White. + * + * If xterm is compiled with the 16-color support disabled, it + * supports the following, from rxvt: + * Ps = 1 0 0 -> Set foreground and background color to + * default. + * + * If 88- or 256-color support is compiled, the following apply. + * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second + * Ps. + * Ps = 4 8 ; 5 ; Ps -> Set background color to the second + * Ps. + */Terminal.prototype.charAttributes=function(params){// Optimize a single SGR0. +if(params.length===1&¶ms[0]===0){this.curAttr=this.defAttr;return;}var l=params.length,i=0,flags=this.curAttr>>18,fg=this.curAttr>>9&0x1ff,bg=this.curAttr&0x1ff,p;for(;i=30&&p<=37){// fg color 8 +fg=p-30;}else if(p>=40&&p<=47){// bg color 8 +bg=p-40;}else if(p>=90&&p<=97){// fg color 16 +p+=8;fg=p-90;}else if(p>=100&&p<=107){// bg color 16 +p+=8;bg=p-100;}else if(p===0){// default +flags=this.defAttr>>18;fg=this.defAttr>>9&0x1ff;bg=this.defAttr&0x1ff;// flags = 0; +// fg = 0x1ff; +// bg = 0x1ff; +}else if(p===1){// bold text +flags|=1;}else if(p===4){// underlined text +flags|=2;}else if(p===5){// blink +flags|=4;}else if(p===7){// inverse and positive +// test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' +flags|=8;}else if(p===8){// invisible +flags|=16;}else if(p===22){// not bold +flags&=~1;}else if(p===24){// not underlined +flags&=~2;}else if(p===25){// not blink +flags&=~4;}else if(p===27){// not inverse +flags&=~8;}else if(p===28){// not invisible +flags&=~16;}else if(p===39){// reset fg +fg=this.defAttr>>9&0x1ff;}else if(p===49){// reset bg +bg=this.defAttr&0x1ff;}else if(p===38){// fg color 256 +if(params[i+1]===2){i+=2;fg=matchColor(params[i]&0xff,params[i+1]&0xff,params[i+2]&0xff);if(fg===-1)fg=0x1ff;i+=2;}else if(params[i+1]===5){i+=2;p=params[i]&0xff;fg=p;}}else if(p===48){// bg color 256 +if(params[i+1]===2){i+=2;bg=matchColor(params[i]&0xff,params[i+1]&0xff,params[i+2]&0xff);if(bg===-1)bg=0x1ff;i+=2;}else if(params[i+1]===5){i+=2;p=params[i]&0xff;bg=p;}}else if(p===100){// reset fg/bg +fg=this.defAttr>>9&0x1ff;bg=this.defAttr&0x1ff;}else{this.error('Unknown SGR attribute: %d.',p);}}this.curAttr=flags<<18|fg<<9|bg;};/** + * CSI Ps n Device Status Report (DSR). + * Ps = 5 -> Status Report. Result (``OK'') is + * CSI 0 n + * Ps = 6 -> Report Cursor Position (CPR) [row;column]. + * Result is + * CSI r ; c R + * CSI ? Ps n + * Device Status Report (DSR, DEC-specific). + * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI + * ? r ; c R (assumes page is zero). + * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). + * or CSI ? 1 1 n (not ready). + * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) + * or CSI ? 2 1 n (locked). + * Ps = 2 6 -> Report Keyboard status as + * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). + * The last two parameters apply to VT400 & up, and denote key- + * board ready and LK01 respectively. + * Ps = 5 3 -> Report Locator status as + * CSI ? 5 3 n Locator available, if compiled-in, or + * CSI ? 5 0 n No Locator, if not. + */Terminal.prototype.deviceStatus=function(params){if(!this.prefix){switch(params[0]){case 5:// status report +this.send('\x1b[0n');break;case 6:// cursor position +this.send('\x1b['+(this.y+1)+';'+(this.x+1)+'R');break;}}else if(this.prefix==='?'){// modern xterm doesnt seem to +// respond to any of these except ?6, 6, and 5 +switch(params[0]){case 6:// cursor position +this.send('\x1b[?'+(this.y+1)+';'+(this.x+1)+'R');break;case 15:// no printer +// this.send('\x1b[?11n'); +break;case 25:// dont support user defined keys +// this.send('\x1b[?21n'); +break;case 26:// north american keyboard +// this.send('\x1b[?27;1;0;0n'); +break;case 53:// no dec locator/mouse +// this.send('\x1b[?50n'); +break;}}};/** + * Additions + *//** + * CSI Ps @ + * Insert Ps (Blank) Character(s) (default = 1) (ICH). + */Terminal.prototype.insertChars=function(params){var param,row,j,ch;param=params[0];if(param<1)param=1;row=this.y+this.ybase;j=this.x;ch=[this.eraseAttr(),' ',1];// xterm +while(param--&&j=this.rows){this.y=this.rows-1;}this.x=0;};/** + * CSI Ps F + * Cursor Preceding Line Ps Times (default = 1) (CNL). + * reuse CSI Ps A ? + */Terminal.prototype.cursorPrecedingLine=function(params){var param=params[0];if(param<1)param=1;this.y-=param;if(this.y<0)this.y=0;this.x=0;};/** + * CSI Ps G + * Cursor Character Absolute [column] (default = [row,1]) (CHA). + */Terminal.prototype.cursorCharAbsolute=function(params){var param=params[0];if(param<1)param=1;this.x=param-1;};/** + * CSI Ps L + * Insert Ps Line(s) (default = 1) (IL). + */Terminal.prototype.insertLines=function(params){var param,row,j;param=params[0];if(param<1)param=1;row=this.y+this.ybase;j=this.rows-1-this.scrollBottom;j=this.rows-1+this.ybase-j+1;while(param--){// test: echo -e '\e[44m\e[1L\e[0m' +// blankLine(true) - xterm/linux behavior +this.lines.splice(row,0,this.blankLine(true));this.lines.splice(j,1);}// this.maxRange(); +this.updateRange(this.y);this.updateRange(this.scrollBottom);};/** + * CSI Ps M + * Delete Ps Line(s) (default = 1) (DL). + */Terminal.prototype.deleteLines=function(params){var param,row,j;param=params[0];if(param<1)param=1;row=this.y+this.ybase;j=this.rows-1-this.scrollBottom;j=this.rows-1+this.ybase-j;while(param--){// test: echo -e '\e[44m\e[1M\e[0m' +// blankLine(true) - xterm/linux behavior +this.lines.splice(j+1,0,this.blankLine(true));this.lines.splice(row,1);}// this.maxRange(); +this.updateRange(this.y);this.updateRange(this.scrollBottom);};/** + * CSI Ps P + * Delete Ps Character(s) (default = 1) (DCH). + */Terminal.prototype.deleteChars=function(params){var param,row,ch;param=params[0];if(param<1)param=1;row=this.y+this.ybase;ch=[this.eraseAttr(),' ',1];// xterm +while(param--){this.lines[row].splice(this.x,1);this.lines[row].push(ch);}};/** + * CSI Ps X + * Erase Ps Character(s) (default = 1) (ECH). + */Terminal.prototype.eraseChars=function(params){var param,row,j,ch;param=params[0];if(param<1)param=1;row=this.y+this.ybase;j=this.x;ch=[this.eraseAttr(),' ',1];// xterm +while(param--&&j=this.cols){this.x=this.cols-1;}};/** + * 141 61 a * HPR - + * Horizontal Position Relative + * reuse CSI Ps C ? + */Terminal.prototype.HPositionRelative=function(params){var param=params[0];if(param<1)param=1;this.x+=param;if(this.x>=this.cols){this.x=this.cols-1;}};/** + * CSI Ps c Send Device Attributes (Primary DA). + * Ps = 0 or omitted -> request attributes from terminal. The + * response depends on the decTerminalID resource setting. + * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') + * -> CSI ? 1 ; 0 c (``VT101 with No Options'') + * -> CSI ? 6 c (``VT102'') + * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') + * The VT100-style response parameters do not mean anything by + * themselves. VT220 parameters do, telling the host what fea- + * tures the terminal supports: + * Ps = 1 -> 132-columns. + * Ps = 2 -> Printer. + * Ps = 6 -> Selective erase. + * Ps = 8 -> User-defined keys. + * Ps = 9 -> National replacement character sets. + * Ps = 1 5 -> Technical characters. + * Ps = 2 2 -> ANSI color, e.g., VT525. + * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). + * CSI > Ps c + * Send Device Attributes (Secondary DA). + * Ps = 0 or omitted -> request the terminal's identification + * code. The response depends on the decTerminalID resource set- + * ting. It should apply only to VT220 and up, but xterm extends + * this to VT100. + * -> CSI > Pp ; Pv ; Pc c + * where Pp denotes the terminal type + * Pp = 0 -> ``VT100''. + * Pp = 1 -> ``VT220''. + * and Pv is the firmware version (for xterm, this was originally + * the XFree86 patch number, starting with 95). In a DEC termi- + * nal, Pc indicates the ROM cartridge registration number and is + * always zero. + * More information: + * xterm/charproc.c - line 2012, for more information. + * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) + */Terminal.prototype.sendDeviceAttributes=function(params){if(params[0]>0)return;if(!this.prefix){if(this.is('xterm')||this.is('rxvt-unicode')||this.is('screen')){this.send('\x1b[?1;2c');}else if(this.is('linux')){this.send('\x1b[?6c');}}else if(this.prefix==='>'){// xterm and urxvt +// seem to spit this +// out around ~370 times (?). +if(this.is('xterm')){this.send('\x1b[>0;276;0c');}else if(this.is('rxvt-unicode')){this.send('\x1b[>85;95;0c');}else if(this.is('linux')){// not supported by linux console. +// linux console echoes parameters. +this.send(params[0]+'c');}else if(this.is('screen')){this.send('\x1b[>83;40003;0c');}}};/** + * CSI Pm d + * Line Position Absolute [row] (default = [1,column]) (VPA). + */Terminal.prototype.linePosAbsolute=function(params){var param=params[0];if(param<1)param=1;this.y=param-1;if(this.y>=this.rows){this.y=this.rows-1;}};/** + * 145 65 e * VPR - Vertical Position Relative + * reuse CSI Ps B ? + */Terminal.prototype.VPositionRelative=function(params){var param=params[0];if(param<1)param=1;this.y+=param;if(this.y>=this.rows){this.y=this.rows-1;}};/** + * CSI Ps ; Ps f + * Horizontal and Vertical Position [row;column] (default = + * [1,1]) (HVP). + */Terminal.prototype.HVPosition=function(params){if(params[0]<1)params[0]=1;if(params[1]<1)params[1]=1;this.y=params[0]-1;if(this.y>=this.rows){this.y=this.rows-1;}this.x=params[1]-1;if(this.x>=this.cols){this.x=this.cols-1;}};/** + * CSI Pm h Set Mode (SM). + * Ps = 2 -> Keyboard Action Mode (AM). + * Ps = 4 -> Insert Mode (IRM). + * Ps = 1 2 -> Send/receive (SRM). + * Ps = 2 0 -> Automatic Newline (LNM). + * CSI ? Pm h + * DEC Private Mode Set (DECSET). + * Ps = 1 -> Application Cursor Keys (DECCKM). + * Ps = 2 -> Designate USASCII for character sets G0-G3 + * (DECANM), and set VT100 mode. + * Ps = 3 -> 132 Column Mode (DECCOLM). + * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). + * Ps = 5 -> Reverse Video (DECSCNM). + * Ps = 6 -> Origin Mode (DECOM). + * Ps = 7 -> Wraparound Mode (DECAWM). + * Ps = 8 -> Auto-repeat Keys (DECARM). + * Ps = 9 -> Send Mouse X & Y on button press. See the sec- + * tion Mouse Tracking. + * Ps = 1 0 -> Show toolbar (rxvt). + * Ps = 1 2 -> Start Blinking Cursor (att610). + * Ps = 1 8 -> Print form feed (DECPFF). + * Ps = 1 9 -> Set print extent to full screen (DECPEX). + * Ps = 2 5 -> Show Cursor (DECTCEM). + * Ps = 3 0 -> Show scrollbar (rxvt). + * Ps = 3 5 -> Enable font-shifting functions (rxvt). + * Ps = 3 8 -> Enter Tektronix Mode (DECTEK). + * Ps = 4 0 -> Allow 80 -> 132 Mode. + * Ps = 4 1 -> more(1) fix (see curses resource). + * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- + * RCM). + * Ps = 4 4 -> Turn On Margin Bell. + * Ps = 4 5 -> Reverse-wraparound Mode. + * Ps = 4 6 -> Start Logging. This is normally disabled by a + * compile-time option. + * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- + * abled by the titeInhibit resource). + * Ps = 6 6 -> Application keypad (DECNKM). + * Ps = 6 7 -> Backarrow key sends backspace (DECBKM). + * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and + * release. See the section Mouse Tracking. + * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. + * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. + * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. + * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. + * Ps = 1 0 0 5 -> Enable Extended Mouse Mode. + * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). + * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). + * Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. + * (enables the eightBitInput resource). + * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- + * Lock keys. (This enables the numLock resource). + * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This + * enables the metaSendsEscape resource). + * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete + * key. + * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This + * enables the altSendsEscape resource). + * Ps = 1 0 4 0 -> Keep selection even if not highlighted. + * (This enables the keepSelection resource). + * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables + * the selectToClipboard resource). + * Ps = 1 0 4 2 -> Enable Urgency window manager hint when + * Control-G is received. (This enables the bellIsUrgent + * resource). + * Ps = 1 0 4 3 -> Enable raising of the window when Control-G + * is received. (enables the popOnBell resource). + * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be + * disabled by the titeInhibit resource). + * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- + * abled by the titeInhibit resource). + * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate + * Screen Buffer, clearing it first. (This may be disabled by + * the titeInhibit resource). This combines the effects of the 1 + * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based + * applications rather than the 4 7 mode. + * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. + * Ps = 1 0 5 1 -> Set Sun function-key mode. + * Ps = 1 0 5 2 -> Set HP function-key mode. + * Ps = 1 0 5 3 -> Set SCO function-key mode. + * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). + * Ps = 1 0 6 1 -> Set VT220 keyboard emulation. + * Ps = 2 0 0 4 -> Set bracketed paste mode. + * Modes: + * http: *vt100.net/docs/vt220-rm/chapter4.html + */Terminal.prototype.setMode=function(params){if((typeof params==='undefined'?'undefined':_typeof(params))==='object'){var l=params.length,i=0;for(;i1000;this.mouseEvents=true;this.element.style.cursor='default';this.log('Binding to mouse events.');break;case 1004:// send focusin/focusout events +// focusin: ^[[I +// focusout: ^[[O +this.sendFocus=true;break;case 1005:// utf8 ext mode mouse +this.utfMouse=true;// for wide terminals +// simply encodes large values as utf8 characters +break;case 1006:// sgr ext mode mouse +this.sgrMouse=true;// for wide terminals +// does not add 32 to fields +// press: ^[[ Keyboard Action Mode (AM). + * Ps = 4 -> Replace Mode (IRM). + * Ps = 1 2 -> Send/receive (SRM). + * Ps = 2 0 -> Normal Linefeed (LNM). + * CSI ? Pm l + * DEC Private Mode Reset (DECRST). + * Ps = 1 -> Normal Cursor Keys (DECCKM). + * Ps = 2 -> Designate VT52 mode (DECANM). + * Ps = 3 -> 80 Column Mode (DECCOLM). + * Ps = 4 -> Jump (Fast) Scroll (DECSCLM). + * Ps = 5 -> Normal Video (DECSCNM). + * Ps = 6 -> Normal Cursor Mode (DECOM). + * Ps = 7 -> No Wraparound Mode (DECAWM). + * Ps = 8 -> No Auto-repeat Keys (DECARM). + * Ps = 9 -> Don't send Mouse X & Y on button press. + * Ps = 1 0 -> Hide toolbar (rxvt). + * Ps = 1 2 -> Stop Blinking Cursor (att610). + * Ps = 1 8 -> Don't print form feed (DECPFF). + * Ps = 1 9 -> Limit print to scrolling region (DECPEX). + * Ps = 2 5 -> Hide Cursor (DECTCEM). + * Ps = 3 0 -> Don't show scrollbar (rxvt). + * Ps = 3 5 -> Disable font-shifting functions (rxvt). + * Ps = 4 0 -> Disallow 80 -> 132 Mode. + * Ps = 4 1 -> No more(1) fix (see curses resource). + * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- + * NRCM). + * Ps = 4 4 -> Turn Off Margin Bell. + * Ps = 4 5 -> No Reverse-wraparound Mode. + * Ps = 4 6 -> Stop Logging. (This is normally disabled by a + * compile-time option). + * Ps = 4 7 -> Use Normal Screen Buffer. + * Ps = 6 6 -> Numeric keypad (DECNKM). + * Ps = 6 7 -> Backarrow key sends delete (DECBKM). + * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and + * release. See the section Mouse Tracking. + * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. + * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. + * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. + * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. + * Ps = 1 0 0 5 -> Disable Extended Mouse Mode. + * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output + * (rxvt). + * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). + * Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables + * the eightBitInput resource). + * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- + * Lock keys. (This disables the numLock resource). + * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. + * (This disables the metaSendsEscape resource). + * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad + * Delete key. + * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. + * (This disables the altSendsEscape resource). + * Ps = 1 0 4 0 -> Do not keep selection when not highlighted. + * (This disables the keepSelection resource). + * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables + * the selectToClipboard resource). + * Ps = 1 0 4 2 -> Disable Urgency window manager hint when + * Control-G is received. (This disables the bellIsUrgent + * resource). + * Ps = 1 0 4 3 -> Disable raising of the window when Control- + * G is received. (This disables the popOnBell resource). + * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen + * first if in the Alternate Screen. (This may be disabled by + * the titeInhibit resource). + * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be + * disabled by the titeInhibit resource). + * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor + * as in DECRC. (This may be disabled by the titeInhibit + * resource). This combines the effects of the 1 0 4 7 and 1 0 + * 4 8 modes. Use this with terminfo-based applications rather + * than the 4 7 mode. + * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. + * Ps = 1 0 5 1 -> Reset Sun function-key mode. + * Ps = 1 0 5 2 -> Reset HP function-key mode. + * Ps = 1 0 5 3 -> Reset SCO function-key mode. + * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). + * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. + * Ps = 2 0 0 4 -> Reset bracketed paste mode. + */Terminal.prototype.resetMode=function(params){if((typeof params==='undefined'?'undefined':_typeof(params))==='object'){var l=params.length,i=0;for(;i Ps; Ps T + * Reset one or more features of the title modes to the default + * value. Normally, "reset" disables the feature. It is possi- + * ble to disable the ability to reset features by compiling a + * different default for the title modes into xterm. + * Ps = 0 -> Do not set window/icon labels using hexadecimal. + * Ps = 1 -> Do not query window/icon labels using hexadeci- + * mal. + * Ps = 2 -> Do not set window/icon labels using UTF-8. + * Ps = 3 -> Do not query window/icon labels using UTF-8. + * (See discussion of "Title Modes"). + */Terminal.prototype.resetTitleModes=function(params){;};/** + * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + */Terminal.prototype.cursorBackwardTab=function(params){var param=params[0]||1;while(param--){this.x=this.prevStop();}};/** + * CSI Ps b Repeat the preceding graphic character Ps times (REP). + */Terminal.prototype.repeatPrecedingCharacter=function(params){var param=params[0]||1,line=this.lines[this.ybase+this.y],ch=line[this.x-1]||[this.defAttr,' ',1];while(param--){line[this.x++]=ch;}};/** + * CSI Ps g Tab Clear (TBC). + * Ps = 0 -> Clear Current Column (default). + * Ps = 3 -> Clear All. + * Potentially: + * Ps = 2 -> Clear Stops on Line. + * http://vt100.net/annarbor/aaa-ug/section6.html + */Terminal.prototype.tabClear=function(params){var param=params[0];if(param<=0){delete this.tabs[this.x];}else if(param===3){this.tabs={};}};/** + * CSI Pm i Media Copy (MC). + * Ps = 0 -> Print screen (default). + * Ps = 4 -> Turn off printer controller mode. + * Ps = 5 -> Turn on printer controller mode. + * CSI ? Pm i + * Media Copy (MC, DEC-specific). + * Ps = 1 -> Print line containing cursor. + * Ps = 4 -> Turn off autoprint mode. + * Ps = 5 -> Turn on autoprint mode. + * Ps = 1 0 -> Print composed display, ignores DECPEX. + * Ps = 1 1 -> Print all pages. + */Terminal.prototype.mediaCopy=function(params){;};/** + * CSI > Ps; Ps m + * Set or reset resource-values used by xterm to decide whether + * to construct escape sequences holding information about the + * modifiers pressed with a given key. The first parameter iden- + * tifies the resource to set/reset. The second parameter is the + * value to assign to the resource. If the second parameter is + * omitted, the resource is reset to its initial value. + * Ps = 1 -> modifyCursorKeys. + * Ps = 2 -> modifyFunctionKeys. + * Ps = 4 -> modifyOtherKeys. + * If no parameters are given, all resources are reset to their + * initial values. + */Terminal.prototype.setResources=function(params){;};/** + * CSI > Ps n + * Disable modifiers which may be enabled via the CSI > Ps; Ps m + * sequence. This corresponds to a resource value of "-1", which + * cannot be set with the other sequence. The parameter identi- + * fies the resource to be disabled: + * Ps = 1 -> modifyCursorKeys. + * Ps = 2 -> modifyFunctionKeys. + * Ps = 4 -> modifyOtherKeys. + * If the parameter is omitted, modifyFunctionKeys is disabled. + * When modifyFunctionKeys is disabled, xterm uses the modifier + * keys to make an extended sequence of functions rather than + * adding a parameter to each function key to denote the modi- + * fiers. + */Terminal.prototype.disableModifiers=function(params){;};/** + * CSI > Ps p + * Set resource value pointerMode. This is used by xterm to + * decide whether to hide the pointer cursor as the user types. + * Valid values for the parameter: + * Ps = 0 -> never hide the pointer. + * Ps = 1 -> hide if the mouse tracking mode is not enabled. + * Ps = 2 -> always hide the pointer. If no parameter is + * given, xterm uses the default, which is 1 . + */Terminal.prototype.setPointerMode=function(params){;};/** + * CSI ! p Soft terminal reset (DECSTR). + * http://vt100.net/docs/vt220-rm/table4-10.html + */Terminal.prototype.softReset=function(params){this.cursorHidden=false;this.insertMode=false;this.originMode=false;this.wraparoundMode=false;// autowrap +this.applicationKeypad=false;// ? +this.viewport.syncScrollArea();this.applicationCursor=false;this.scrollTop=0;this.scrollBottom=this.rows-1;this.curAttr=this.defAttr;this.x=this.y=0;// ? +this.charset=null;this.glevel=0;// ?? +this.charsets=[null];// ?? +};/** + * CSI Ps$ p + * Request ANSI mode (DECRQM). For VT300 and up, reply is + * CSI Ps; Pm$ y + * where Ps is the mode number as in RM, and Pm is the mode + * value: + * 0 - not recognized + * 1 - set + * 2 - reset + * 3 - permanently set + * 4 - permanently reset + */Terminal.prototype.requestAnsiMode=function(params){;};/** + * CSI ? Ps$ p + * Request DEC private mode (DECRQM). For VT300 and up, reply is + * CSI ? Ps; Pm$ p + * where Ps is the mode number as in DECSET, Pm is the mode value + * as in the ANSI DECRQM. + */Terminal.prototype.requestPrivateMode=function(params){;};/** + * CSI Ps ; Ps " p + * Set conformance level (DECSCL). Valid values for the first + * parameter: + * Ps = 6 1 -> VT100. + * Ps = 6 2 -> VT200. + * Ps = 6 3 -> VT300. + * Valid values for the second parameter: + * Ps = 0 -> 8-bit controls. + * Ps = 1 -> 7-bit controls (always set for VT100). + * Ps = 2 -> 8-bit controls. + */Terminal.prototype.setConformanceLevel=function(params){;};/** + * CSI Ps q Load LEDs (DECLL). + * Ps = 0 -> Clear all LEDS (default). + * Ps = 1 -> Light Num Lock. + * Ps = 2 -> Light Caps Lock. + * Ps = 3 -> Light Scroll Lock. + * Ps = 2 1 -> Extinguish Num Lock. + * Ps = 2 2 -> Extinguish Caps Lock. + * Ps = 2 3 -> Extinguish Scroll Lock. + */Terminal.prototype.loadLEDs=function(params){;};/** + * CSI Ps SP q + * Set cursor style (DECSCUSR, VT520). + * Ps = 0 -> blinking block. + * Ps = 1 -> blinking block (default). + * Ps = 2 -> steady block. + * Ps = 3 -> blinking underline. + * Ps = 4 -> steady underline. + */Terminal.prototype.setCursorStyle=function(params){;};/** + * CSI Ps " q + * Select character protection attribute (DECSCA). Valid values + * for the parameter: + * Ps = 0 -> DECSED and DECSEL can erase (default). + * Ps = 1 -> DECSED and DECSEL cannot erase. + * Ps = 2 -> DECSED and DECSEL can erase. + */Terminal.prototype.setCharProtectionAttr=function(params){;};/** + * CSI ? Pm r + * Restore DEC Private Mode Values. The value of Ps previously + * saved is restored. Ps values are the same as for DECSET. + */Terminal.prototype.restorePrivateValues=function(params){;};/** + * CSI Pt; Pl; Pb; Pr; Ps$ r + * Change Attributes in Rectangular Area (DECCARA), VT400 and up. + * Pt; Pl; Pb; Pr denotes the rectangle. + * Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7. + * NOTE: xterm doesn't enable this code by default. + */Terminal.prototype.setAttrInRectangle=function(params){var t=params[0],l=params[1],b=params[2],r=params[3],attr=params[4];var line,i;for(;t Locator disabled (default). + * Ps = 1 -> Locator enabled. + * Ps = 2 -> Locator enabled for one report, then disabled. + * The second parameter specifies the coordinate unit for locator + * reports. + * Valid values for the second parameter: + * Pu = 0 <- or omitted -> default to character cells. + * Pu = 1 <- device physical pixels. + * Pu = 2 <- character cells. + */Terminal.prototype.enableLocatorReporting=function(params){var val=params[0]>0;//this.mouseEvents = val; +//this.decLocator = val; +};/** + * CSI Pt; Pl; Pb; Pr$ z + * Erase Rectangular Area (DECERA), VT400 and up. + * Pt; Pl; Pb; Pr denotes the rectangle. + * NOTE: xterm doesn't enable this code by default. + */Terminal.prototype.eraseRectangle=function(params){var t=params[0],l=params[1],b=params[2],r=params[3];var line,i,ch;ch=[this.eraseAttr(),' ',1];// xterm? +for(;t47);}function matchColor(r1,g1,b1){var hash=r1<<16|g1<<8|b1;if(matchColor._cache[hash]!=null){return matchColor._cache[hash];}var ldiff=Infinity,li=-1,i=0,c,r2,g2,b2,diff;for(;iCOMBINING[max][1])return false;while(max>=min){mid=Math.floor((min+max)/2);if(ucs>COMBINING[mid][1])min=mid+1;else if(ucs=0x7f&&ucs<0xa0)return opts.control;// binary search in table of non-spacing characters +if(bisearch(ucs))return 0;// if we arrive here, ucs is not a combining or C0/C1 control character +return 1+(ucs>=0x1100&&(ucs<=0x115f||// Hangul Jamo init. consonants +ucs==0x2329||ucs==0x232a||ucs>=0x2e80&&ucs<=0xa4cf&&ucs!=0x303f||// CJK..Yi +ucs>=0xac00&&ucs<=0xd7a3||// Hangul Syllables +ucs>=0xf900&&ucs<=0xfaff||// CJK Compat Ideographs +ucs>=0xfe10&&ucs<=0xfe19||// Vertical forms +ucs>=0xfe30&&ucs<=0xfe6f||// CJK Compat Forms +ucs>=0xff00&&ucs<=0xff60||// Fullwidth Forms +ucs>=0xffe0&&ucs<=0xffe6||ucs>=0x20000&&ucs<=0x2fffd||ucs>=0x30000&&ucs<=0x3fffd));}return wcwidth;}({nul:0,control:0});// configurable options +/** + * Expose + */Terminal.EventEmitter=_EventEmitter.EventEmitter;Terminal.CompositionHelper=_CompositionHelper.CompositionHelper;Terminal.Viewport=_Viewport.Viewport;Terminal.inherits=inherits;/** + * Adds an event listener to the terminal. + * + * @param {string} event The name of the event. TODO: Document all event types + * @param {function} callback The function to call when the event is triggered. + */Terminal.on=on;Terminal.off=off;Terminal.cancel=cancel;module.exports=Terminal; + +},{"./CompositionHelper.js":1,"./EventEmitter.js":2,"./Viewport.js":3,"./handlers/Clipboard.js":4,"./utils/Browser":5}]},{},[7])(7) +}); +//# sourceMappingURL=xterm.js.map