├── .github ├── ISSUE_TEMPLATE.md ├── dependabot.yml ├── release.yml └── workflows │ ├── gh-pages.yml │ ├── push_gem.yml │ ├── test.yml │ └── truffle-ruby-test.yml ├── .gitignore ├── .rdoc_options ├── .rubocop.yml ├── CONTRIBUTING.md ├── EXTEND_IRB.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── doc ├── .document ├── COMMAND_LINE_OPTIONS.md ├── COMPARED_WITH_PRY.md ├── Configurations.md ├── EXTEND_IRB.md ├── Index.md └── irb │ ├── irb-tools.rd.ja │ └── irb.rd.ja ├── exe └── irb ├── irb.gemspec ├── lib ├── .document ├── irb.rb └── irb │ ├── .document │ ├── cmd │ └── nop.rb │ ├── color.rb │ ├── color_printer.rb │ ├── command.rb │ ├── command │ ├── backtrace.rb │ ├── base.rb │ ├── break.rb │ ├── catch.rb │ ├── cd.rb │ ├── chws.rb │ ├── context.rb │ ├── continue.rb │ ├── copy.rb │ ├── debug.rb │ ├── delete.rb │ ├── disable_irb.rb │ ├── edit.rb │ ├── exit.rb │ ├── finish.rb │ ├── force_exit.rb │ ├── help.rb │ ├── history.rb │ ├── info.rb │ ├── internal_helpers.rb │ ├── irb_info.rb │ ├── load.rb │ ├── ls.rb │ ├── measure.rb │ ├── next.rb │ ├── pushws.rb │ ├── show_doc.rb │ ├── show_source.rb │ ├── step.rb │ ├── subirb.rb │ └── whereami.rb │ ├── completion.rb │ ├── context.rb │ ├── debug.rb │ ├── debug │ └── ui.rb │ ├── default_commands.rb │ ├── easter-egg.rb │ ├── ext │ ├── change-ws.rb │ ├── eval_history.rb │ ├── loader.rb │ ├── multi-irb.rb │ ├── tracer.rb │ ├── use-loader.rb │ └── workspaces.rb │ ├── frame.rb │ ├── help.rb │ ├── helper_method.rb │ ├── helper_method │ ├── base.rb │ └── conf.rb │ ├── history.rb │ ├── init.rb │ ├── input-method.rb │ ├── inspector.rb │ ├── lc │ ├── error.rb │ ├── help-message │ └── ja │ │ ├── error.rb │ │ └── help-message │ ├── locale.rb │ ├── nesting_parser.rb │ ├── notifier.rb │ ├── output-method.rb │ ├── pager.rb │ ├── ruby-lex.rb │ ├── ruby_logo.aa │ ├── source_finder.rb │ ├── statement.rb │ ├── version.rb │ ├── workspace.rb │ ├── ws-for-case-2.rb │ └── xmp.rb ├── man └── irb.1 └── test ├── irb ├── command │ ├── test_cd.rb │ ├── test_command_aliasing.rb │ ├── test_copy.rb │ ├── test_custom_command.rb │ ├── test_disable_irb.rb │ ├── test_force_exit.rb │ ├── test_help.rb │ ├── test_multi_irb_commands.rb │ └── test_show_source.rb ├── helper.rb ├── test_color.rb ├── test_color_printer.rb ├── test_command.rb ├── test_completion.rb ├── test_context.rb ├── test_debugger_integration.rb ├── test_eval_history.rb ├── test_evaluation.rb ├── test_helper_method.rb ├── test_history.rb ├── test_init.rb ├── test_input_method.rb ├── test_irb.rb ├── test_locale.rb ├── test_nesting_parser.rb ├── test_option.rb ├── test_pager.rb ├── test_raise_exception.rb ├── test_ruby_lex.rb ├── test_tracer.rb ├── test_type_completor.rb ├── test_workspace.rb └── yamatanooroti │ └── test_rendering.rb └── lib └── helper.rb /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | What are your expected behavior and actual behavior of your environment? 4 | 5 | ## Result of irb_info 6 | 7 | Please paste the result of `irb_info` command in IRB. 8 | 9 | ex.) 10 | 11 | ``` 12 | irb(main):001:0> irb_info 13 | => 14 | Ruby version: 3.0.0 15 | IRB version: irb 1.2.7 (2020-09-19) 16 | InputMethod: RelineInputMethod with Reline 0.1.9 and /home/aycabta/.inputrc 17 | .irbrc path: /home/aycabta/.irbrc 18 | ``` 19 | 20 | ## Terminal Emulator 21 | 22 | What's your terminal emulator? 23 | 24 | ## Setting Files 25 | 26 | Are you using `~/.irbrc` and `~/.inputrc`? 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # release.yml 2 | 3 | changelog: 4 | categories: 5 | - title: ✨ Enhancements 6 | labels: 7 | - enhancement 8 | - title: 🐛 Bug Fixes 9 | labels: 10 | - bug 11 | - title: 📚 Documentation 12 | labels: 13 | - documentation 14 | - title: 🛠 Other Changes 15 | labels: 16 | - "*" 17 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy IRB documentation to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | if: ${{ github.repository == 'ruby/irb' && !startsWith(github.event_name, 'pull') }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup Ruby 25 | uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0 26 | with: 27 | ruby-version: "3.3" 28 | bundler-cache: true 29 | - name: Setup Pages 30 | id: pages 31 | uses: actions/configure-pages@v5 32 | - name: Build with IRB 33 | run: bundle exec rake rdoc 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v3 36 | 37 | deploy: 38 | environment: 39 | name: github-pages 40 | url: ${{ steps.deployment.outputs.page_url }} 41 | runs-on: ubuntu-latest 42 | needs: build 43 | steps: 44 | - name: Deploy to GitHub Pages 45 | id: deployment 46 | uses: actions/deploy-pages@v4 47 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/irb' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/irb 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: "ruby" 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "30 14 * * *" 8 | 9 | jobs: 10 | ruby-versions: 11 | if: github.repository == 'ruby/irb' || github.event_name != 'schedule' 12 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 13 | with: 14 | engine: cruby 15 | min_version: 2.7 16 | lint: 17 | if: github.repository == 'ruby/irb' || github.event_name != 'schedule' 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up Ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: "3.3" 25 | bundler-cache: true 26 | - name: Run rubocop 27 | run: bundle exec rubocop 28 | irb: 29 | needs: ruby-versions 30 | name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} 31 | strategy: 32 | matrix: 33 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 34 | with_latest_reline: [true, false] 35 | fail-fast: false 36 | runs-on: ubuntu-latest 37 | env: 38 | WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} 39 | timeout-minutes: 30 40 | steps: 41 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 42 | - name: Set up Ruby 43 | uses: ruby/setup-ruby@v1 44 | with: 45 | ruby-version: ${{ matrix.ruby }} 46 | bundler-cache: true 47 | # Added to make Ruby 2.7 correctly require installed default gems, like `pp`. 48 | rubygems: latest 49 | - name: Run tests 50 | run: bundle exec rake test 51 | - name: Run tests in isolation 52 | run: bundle exec rake test_in_isolation 53 | debug-test: 54 | if: github.repository == 'ruby/irb' || github.event_name != 'schedule' 55 | name: Debug compatibility test 56 | runs-on: ubuntu-latest 57 | timeout-minutes: 30 58 | steps: 59 | - uses: actions/checkout@v4 60 | - name: Set up Ruby 61 | uses: ruby/setup-ruby@v1 62 | with: 63 | ruby-version: 3.3 64 | - name: Install dependencies 65 | run: bundle install 66 | - name: Install IRB 67 | run: | 68 | rake install 69 | - name: Download ruby/debug 70 | run: | 71 | git clone https://github.com/ruby/debug 72 | - name: Run debug tests 73 | working-directory: ./debug 74 | run: | 75 | bundle install 76 | bundle exec rake 77 | vterm-yamatanooroti: 78 | needs: ruby-versions 79 | name: >- 80 | vterm-yamatanooroti ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} 81 | runs-on: ubuntu-latest 82 | strategy: 83 | matrix: 84 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 85 | with_latest_reline: [true, false] 86 | fail-fast: false 87 | env: 88 | WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} 89 | WITH_VTERM: 1 90 | timeout-minutes: 30 91 | steps: 92 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 93 | - name: Install libvterm 94 | run: | 95 | sudo apt install -y libtool-bin 96 | wget http://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz 97 | tar xvzf libvterm-0.1.4.tar.gz 98 | cd libvterm-0.1.4 99 | sed -i -e 's/^PREFIX=.*$/PREFIX=\/usr/g' Makefile 100 | make 101 | sudo make install 102 | - name: Set up Ruby 103 | uses: ruby/setup-ruby@v1 104 | with: 105 | ruby-version: ${{ matrix.ruby }} 106 | bundler-cache: true 107 | - name: rake test_yamatanooroti 108 | run: bundle exec rake test_yamatanooroti 109 | -------------------------------------------------------------------------------- /.github/workflows/truffle-ruby-test.yml: -------------------------------------------------------------------------------- 1 | name: build-with-truffleruby-head 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "30 14 * * *" 8 | 9 | jobs: 10 | irb: 11 | if: github.repository == 'ruby/irb' || github.event_name != 'schedule' 12 | name: rake test truffleruby-head ${{ matrix.with_latest_reline && '(latest reline)' || '' }} 13 | strategy: 14 | matrix: 15 | with_latest_reline: [true, false] 16 | fail-fast: false 17 | runs-on: ubuntu-latest 18 | env: 19 | WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} 20 | timeout-minutes: 30 21 | steps: 22 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 23 | - name: Set up Ruby 24 | uses: ruby/setup-ruby@v1 25 | with: 26 | ruby-version: truffleruby-head 27 | bundler-cache: true 28 | - name: Run tests 29 | run: bundle exec rake test 30 | - name: Run tests in isolation 31 | run: bundle exec rake test_in_isolation 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /pkg/ 6 | /spec/reports/ 7 | /tmp/ 8 | Gemfile.lock 9 | _site/ 10 | -------------------------------------------------------------------------------- /.rdoc_options: -------------------------------------------------------------------------------- 1 | page_dir: doc 2 | warn_missing_rdoc_ref: true 3 | 4 | autolink_excluded_words: 5 | - IRB 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 3.0 3 | DisabledByDefault: true 4 | SuggestExtensions: false 5 | 6 | Layout/TrailingWhitespace: 7 | Enabled: true 8 | 9 | Layout/TrailingEmptyLines: 10 | Enabled: true 11 | 12 | Layout/IndentationConsistency: 13 | Enabled: true 14 | 15 | Layout/CaseIndentation: 16 | Enabled: true 17 | EnforcedStyle: end 18 | 19 | Layout/CommentIndentation: 20 | Enabled: true 21 | 22 | Layout/IndentationStyle: 23 | Enabled: true 24 | 25 | Layout/SpaceAroundKeyword: 26 | Enabled: true 27 | 28 | Layout/SpaceBeforeComma: 29 | Enabled: true 30 | 31 | Layout/SpaceAfterComma: 32 | Enabled: true 33 | 34 | Style/ClassMethodsDefinitions: 35 | Enabled: true 36 | EnforcedStyle: self_class 37 | 38 | Layout/EmptyLines: 39 | Enabled: true 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to IRB 2 | 3 | Bug reports and pull requests are welcome on GitHub at [https://github.com/ruby/irb](https://github.com/ruby/irb). 4 | 5 | ## Set up the environment 6 | 7 | 1. Fork the project to your GitHub account. 8 | 2. Clone the fork with `git clone git@github.com:[your_username]/irb.git`. 9 | 3. Run `bundle install`. 10 | 4. Run `bundle exec rake` to make sure tests pass locally. 11 | 12 | ## Run integration tests 13 | 14 | If your changes affect component rendering, such as the autocompletion's dialog/dropdown, you may need to run IRB's integration tests, known as `yamatanooroti`. 15 | 16 | Before running these tests, ensure that you have `libvterm` installed. If you're using Homebrew, you can install it by running: 17 | 18 | ```bash 19 | brew install libvterm 20 | ``` 21 | 22 | After installing `libvterm`, you can run the integration tests using the following commands: 23 | 24 | ```bash 25 | WITH_VTERM=1 bundle install 26 | WITH_VTERM=1 bundle exec rake test test_yamatanooroti 27 | ``` 28 | 29 | ## Update documentation 30 | 31 | IRB's documentation is generated by [RDoc](https://ruby.github.io/rdoc/) and published to [ruby.github.io/irb](https://ruby.github.io/irb/). Most of the documentation source lives under the `doc/` directory. 32 | 33 | Run the following command to generate the documentation site locally. 34 | 35 | ```bash 36 | bundle exec rake rdoc 37 | bundle exec rake rerdoc # to force regeneration 38 | ``` 39 | 40 | Follow the output message to open the documentation site in your browser. 41 | 42 | > [!Note] 43 | > 44 | > Please ensure that the changes are rendered correctly on the documentation site. 45 | > RDoc's Markdown support is limited, so the rendered result on GitHub might differ from what’s rendered on [https://ruby.github.io/irb](https://ruby.github.io/irb). 46 | 47 | We welcome any improvements to the documentation, including: 48 | 49 | - Fixing typos and grammatical errors. 50 | - Adding missing documentation for features. 51 | - Adding missing documentation for configuration options. 52 | - Adding demo images/gifs for features. 53 | -------------------------------------------------------------------------------- /EXTEND_IRB.md: -------------------------------------------------------------------------------- 1 | # Extend IRB 2 | 3 | This page has been moved to the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | is_unix = RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i 6 | is_truffleruby = RUBY_DESCRIPTION =~ /truffleruby/ 7 | 8 | if is_unix && ENV['WITH_VTERM'] 9 | gem "vterm", github: "ruby/vterm-gem" 10 | gem "yamatanooroti", github: "ruby/yamatanooroti" 11 | end 12 | 13 | gem "stackprof" if is_unix && !is_truffleruby 14 | 15 | gem "reline", github: "ruby/reline" if ENV["WITH_LATEST_RELINE"] == "true" 16 | gem "rake" 17 | gem "test-unit" 18 | gem "test-unit-ruby-core" 19 | 20 | gem "rubocop" 21 | 22 | gem "tracer" if !is_truffleruby 23 | gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] 24 | 25 | gem "rdoc", ">= 6.11.0" 26 | 27 | if RUBY_VERSION >= "3.0.0" && !is_truffleruby 28 | gem "repl_type_completor" 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IRB 2 | 3 | [![Gem Version](https://badge.fury.io/rb/irb.svg)](https://badge.fury.io/rb/irb) 4 | [![Static Badge](https://img.shields.io/badge/RDoc-flat?style=flat&label=documentation&link=https%3A%2F%2Fruby.github.io%2Firb%2F)](https://ruby.github.io/irb/) 5 | [![build](https://github.com/ruby/irb/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/irb/actions/workflows/test.yml) 6 | 7 | 8 | IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. 9 | 10 | The `irb` command from your shell will start the interpreter. 11 | 12 | ## Installation 13 | 14 | > [!Note] 15 | > 16 | > IRB is a default gem of Ruby so you shouldn't need to install it separately. 17 | > 18 | > But if you're using Ruby 2.6 or later and want to upgrade/install a specific version of IRB, please follow these steps. 19 | 20 | To install it with `bundler`, add this line to your application's Gemfile: 21 | 22 | ```ruby 23 | gem 'irb' 24 | ``` 25 | 26 | And then execute: 27 | 28 | ```shell 29 | $ bundle 30 | ``` 31 | 32 | Or install it directly with: 33 | 34 | ```shell 35 | $ gem install irb 36 | ``` 37 | 38 | ## Usage 39 | 40 | > [!Note] 41 | > 42 | > We're working hard to match Pry's variety of powerful features in IRB, and you can track our progress or find contribution ideas in [this document](https://ruby.github.io/irb/COMPARED_WITH_PRY_md.html). 43 | 44 | ### The `irb` Executable 45 | 46 | You can start a fresh IRB session by typing `irb` in your terminal. 47 | 48 | In the session, you can evaluate Ruby expressions or even prototype a small Ruby script. An input is executed when it is syntactically complete. 49 | 50 | ```shell 51 | $ irb 52 | irb(main):001> 1 + 2 53 | => 3 54 | irb(main):002* class Foo 55 | irb(main):003* def foo 56 | irb(main):004* puts 1 57 | irb(main):005* end 58 | irb(main):006> end 59 | => :foo 60 | irb(main):007> Foo.new.foo 61 | 1 62 | => nil 63 | ``` 64 | 65 | ### The `binding.irb` Breakpoint 66 | 67 | If you use Ruby 2.5 or later versions, you can also use `binding.irb` in your program as breakpoints. 68 | 69 | Once a `binding.irb` is evaluated, a new IRB session will be started with the surrounding context: 70 | 71 | ```shell 72 | $ ruby test.rb 73 | 74 | From: test.rb @ line 2 : 75 | 76 | 1: def greet(word) 77 | => 2: binding.irb 78 | 3: puts "Hello #{word}" 79 | 4: end 80 | 5: 81 | 6: greet("World") 82 | 83 | irb(main):001:0> word 84 | => "World" 85 | irb(main):002:0> exit 86 | Hello World 87 | ``` 88 | 89 | ### Debugging 90 | 91 | You can use IRB as a debugging console with `debug.gem` with these options: 92 | 93 | - In `binding.irb`, use the `debug` command to start an `irb:rdbg` session with access to all `debug.gem` commands. 94 | - Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. 95 | 96 | To learn more about debugging with IRB, see [Debugging with IRB](https://ruby.github.io/irb/#label-Debugging+with+IRB). 97 | 98 | ## Documentation 99 | 100 | https://ruby.github.io/irb/ provides a comprehensive guide to IRB's features and usage. 101 | 102 | ## Configuration 103 | 104 | See the [Configuration page](https://ruby.github.io/irb/Configurations_md.html) in the documentation. 105 | 106 | ## Extending IRB 107 | 108 | IRB `v1.13.0` and later versions allows users/libraries to extend its functionality through official APIs. 109 | 110 | For more information, please visit the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). 111 | 112 | ## Contributing 113 | 114 | See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. 115 | 116 | ## Releasing 117 | 118 | ``` 119 | rake release 120 | gh release create vX.Y.Z --generate-notes 121 | ``` 122 | 123 | ## License 124 | 125 | The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). 126 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | require "rdoc/task" 4 | 5 | Rake::TestTask.new(:test) do |t| 6 | t.libs << "test" << "test/lib" 7 | t.libs << "lib" 8 | t.ruby_opts << "-rhelper" 9 | t.test_files = FileList["test/irb/**/test_*.rb"] 10 | end 11 | 12 | # To make sure they have been correctly setup for Ruby CI. 13 | desc "Run each irb test file in isolation." 14 | task :test_in_isolation do 15 | failed = false 16 | 17 | FileList["test/irb/**/test_*.rb"].each do |test_file| 18 | ENV["TEST"] = test_file 19 | begin 20 | Rake::Task["test"].execute 21 | rescue 22 | failed = true 23 | msg = "Test '#{test_file}' failed when being executed in isolation. Please make sure 'rake test TEST=#{test_file}' passes." 24 | separation_line = '=' * msg.length 25 | 26 | puts <<~MSG 27 | #{separation_line} 28 | #{msg} 29 | #{separation_line} 30 | MSG 31 | end 32 | end 33 | 34 | fail "Some tests failed when being executed in isolation" if failed 35 | end 36 | 37 | Rake::TestTask.new(:test_yamatanooroti) do |t| 38 | t.libs << 'test' << "test/lib" 39 | t.libs << 'lib' 40 | #t.loader = :direct 41 | t.ruby_opts << "-rhelper" 42 | t.pattern = 'test/irb/yamatanooroti/test_*.rb' 43 | end 44 | 45 | task :default => :test 46 | 47 | RDoc::Task.new do |rdoc| 48 | rdoc.title = "IRB Documentation" 49 | rdoc.main = "Index.md" 50 | rdoc.rdoc_dir = "_site" 51 | rdoc.options.push("lib") 52 | end 53 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require_relative "../lib/irb" 5 | 6 | IRB.start(__FILE__) 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /doc/.document: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /doc/COMMAND_LINE_OPTIONS.md: -------------------------------------------------------------------------------- 1 | # Index of Command-Line Options 2 | 3 | These are the IRB command-line options, with links to explanatory text: 4 | 5 | - `-d`: Set `$DEBUG` and {$VERBOSE}[rdoc-ref:IRB@Verbosity] 6 | to `true`. 7 | - `-E _ex_[:_in_]`: Set initial external (ex) and internal (in) 8 | {encodings}[rdoc-ref:IRB@Encodings] (same as `ruby -E`). 9 | - `-f`: Don't initialize from {configuration file}[rdoc-ref:IRB@Configuration+File]. 10 | - `-I _dirpath_`: Specify {$LOAD_PATH directory}[rdoc-ref:IRB@Load+Modules] 11 | (same as `ruby -I`). 12 | - `-r _load-module_`: Require {load-module}[rdoc-ref:IRB@Load+Modules] 13 | (same as `ruby -r`). 14 | - `-U`: Set external and internal {encodings}[rdoc-ref:IRB@Encodings] to UTF-8. 15 | - `-w`: Suppress {warnings}[rdoc-ref:IRB@Warnings] (same as `ruby -w`). 16 | - `-W[_level_]`: Set {warning level}[rdoc-ref:IRB@Warnings]; 17 | 0=silence, 1=medium, 2=verbose (same as `ruby -W`). 18 | - `--autocomplete`: Use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. 19 | - `--back-trace-limit _n_`: Set a {backtrace limit}[rdoc-ref:IRB@Tracer]; 20 | display at most the top `n` and bottom `n` entries. 21 | - `--colorize`: Use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] 22 | for input and output. 23 | - `--context-mode _n_`: Select method to create Binding object 24 | for new {workspace}[rdoc-ref:IRB@Commands]; `n` in range `0..4`. 25 | - `--echo`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 26 | return values. 27 | - `--extra-doc-dir _dirpath_`: 28 | Add a {documentation directory}[rdoc-ref:IRB@RI+Documentation+Directories] 29 | for the documentation dialog. 30 | - `--inf-ruby-mode`: Set prompt mode to {:INF_RUBY}[rdoc-ref:IRB@Pre-Defined+Prompts] 31 | (appropriate for `inf-ruby-mode` on Emacs); 32 | suppresses --multiline and --singleline. 33 | - `--inspect`: Use method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 34 | return values. 35 | - `--multiline`: Use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. 36 | - `--noautocomplete`: Don't use {auto-completion}[rdoc-ref:IRB@Automatic+Completion]. 37 | - `--nocolorize`: Don't use {color-highlighting}[rdoc-ref:IRB@Color+Highlighting] 38 | for input and output. 39 | - `--noecho`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 40 | return values. 41 | - `--noecho-on-assignment`: Don't print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 42 | result on assignment. 43 | - `--noinspect`: Don't se method `inspect` for printing ({echoing}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 44 | return values. 45 | - `--nomultiline`: Don't use the multiline editor as the {input method}[rdoc-ref:IRB@Input+Method]. 46 | - `--noprompt`: Don't print {prompts}[rdoc-ref:IRB@Prompt+and+Return+Formats]. 47 | - `--noscript`: Treat the first command-line argument as a normal 48 | {command-line argument}[rdoc-ref:IRB@Initialization+Script], 49 | and include it in `ARGV`. 50 | - `--nosingleline`: Don't use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. 51 | - `--noverbose`: Don't print {verbose}[rdoc-ref:IRB@Verbosity] details. 52 | - `--prompt _mode_`, `--prompt-mode _mode_`: 53 | Set {prompt and return formats}[rdoc-ref:IRB@Prompt+and+Return+Formats]; 54 | `mode` may be a {pre-defined prompt}[rdoc-ref:IRB@Pre-Defined+Prompts] 55 | or the name of a {custom prompt}[rdoc-ref:IRB@Custom+Prompts]. 56 | - `--script`: Treat the first command-line argument as the path to an 57 | {initialization script}[rdoc-ref:IRB@Initialization+Script], 58 | and omit it from `ARGV`. 59 | - `--simple-prompt`, `--sample-book-mode`: 60 | Set prompt mode to {:SIMPLE}[rdoc-ref:IRB@Pre-Defined+Prompts]. 61 | - `--singleline`: Use the singleline editor as the {input method}[rdoc-ref:IRB@Input+Method]. 62 | - `--tracer`: Use {Tracer}[rdoc-ref:IRB@Tracer] to print a stack trace for each input command. 63 | - `--truncate-echo-on-assignment`: Print ({echo}[rdoc-ref:IRB@Return-Value+Printing+-28Echoing-29]) 64 | truncated result on assignment. 65 | - `--verbose`Print {verbose}[rdoc-ref:IRB@Verbosity] details. 66 | - `-v`, `--version`: Print the {IRB version}[rdoc-ref:IRB@Version]. 67 | - `-h`, `--help`: Print the {IRB help text}[rdoc-ref:IRB@Help]. 68 | - `--`: Separate options from {arguments}[rdoc-ref:IRB@Command-Line+Arguments] 69 | on the command-line. 70 | -------------------------------------------------------------------------------- /doc/COMPARED_WITH_PRY.md: -------------------------------------------------------------------------------- 1 | # Comparison with Pry 2 | 3 | 👋 IRB is on a mission to match feature parity with Pry. This document is here to help us track the similarities and differences between the two. 4 | 5 | Feel free to chip in and update this table - we appreciate your help! 6 | 7 | | Feature | Pry | IRB | Note | 8 | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | 9 | | Supported Rubies | `>= 2.0` | `>= 2.7` | | 10 | | Source code browsing | `show-source` | `show_source` | IRB's `show_source` can't display C source. See [#664](https://github.com/ruby/irb/issues/664) | 11 | | Document browsing | `ri` | `show_doc` | | 12 | | Live help system | `help` or `command_name --help` | `help` | IRB doesn't support detailed descriptions for individual commands yet | 13 | | Open methods in editors | `edit` | `edit` | | 14 | | Syntax highlighting | Yes | Yes | | 15 | | Command shell integration | Yes | No | Currently, there's no plan to support such features in IRB | 16 | | Navigation | - `cd object` to enter `object`
- `cd ..` to leave the current object
- `nesting` to list nesting levels | - `pushws object`
- `popws`
- `workspaces`
- `cd object`/`cd ..` (since [1.14.0](https://github.com/ruby/irb/releases/tag/v1.14.0)) | We plan to refine IRB's commands in the future | 17 | | Runtime invocation | `binding.pry` | `binding.irb` | | 18 | | Command system | Yes | No | Planned in #513 and #588 | 19 | | Input history | [Comprehensive support](https://github.com/pry/pry/wiki/History) | Supports retrieving previous input and the `history` command | The history command doesn't support as many flags yet, but contributions are welcome. | 20 | | Pager support | Command output and return value | Command output and return value | | 21 | | Debugger integration | With `byebug` through [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) gem | Supports [`irb:rdbg`](https://github.com/ruby/irb#debugging-with-irb) sessions | | 22 | | Rails console | Through [`pry-rails`](https://github.com/pry/pry-rails) gem | Rails' default | Rails console with IRB doesn't have commands like `show-routes` or `show-models` | 23 | -------------------------------------------------------------------------------- /doc/EXTEND_IRB.md: -------------------------------------------------------------------------------- 1 | # Extend IRB 2 | 3 | From v1.13.0, IRB provides official APIs to extend its functionality. This feature allows libraries to 4 | customize and enhance their users' IRB sessions by adding new commands and helper methods tailored for 5 | the libraries. 6 | 7 | ## Helper Methods vs. Commands 8 | 9 | - Use a helper method if the operation is meant to return a Ruby object that interacts with the application. 10 | - For example, an `admin_user` helper method that returns `User.where(admin: true).first`, which can then be used like `login_as(admin_user)`. 11 | - Use a command if the operation fits one of the following: 12 | - A utility operation that performs non-Ruby related tasks, such as IRB's `edit` command. 13 | - Displays information, like the `show_source` command. 14 | - If the operation requires non-Ruby syntax arguments, like `ls -g pattern`. 15 | 16 | If you don't know what to pick, go with commands first. Commands are generally safer as they can handle a wider variety of inputs and use cases. 17 | 18 | ## Commands 19 | 20 | Commands are designed to complete certain tasks or display information for the user, similar to shell commands. 21 | Therefore, they are designed to accept a variety of inputs, including those that are not valid Ruby code, such 22 | as `my_cmd Foo#bar` or `my_cmd --flag foo`. 23 | 24 | ### Example 25 | 26 | ```rb 27 | require "irb/command" 28 | 29 | class Greet < IRB::Command::Base 30 | category "Greeting" 31 | description "Greets the user" 32 | help_message <<~HELP 33 | Greets the user with the given name. 34 | 35 | Usage: greet 36 | HELP 37 | 38 | # Any input after the command name will be passed as a single string. 39 | # If nothing is added after the command, an empty string will be passed. 40 | def execute(arg) 41 | puts "Hello! #{arg}" 42 | end 43 | end 44 | 45 | IRB::Command.register(:greet, Greet) 46 | ``` 47 | 48 | As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `greet` will be accessible to the user. 49 | 50 | ```txt 51 | irb(main):001> greet 52 | Hello! 53 | => nil 54 | irb(main):002> greet Stan 55 | Hello! Stan 56 | => nil 57 | ``` 58 | 59 | And because the `Greet` command introduces a new category, `Greeting`, a new help message category will be created: 60 | 61 | ```txt 62 | Help 63 | help List all available commands. Use `help ` to get information about a specific command. 64 | 65 | Greeting 66 | greet Greets the user 67 | 68 | IRB 69 | context Displays current configuration. 70 | ... 71 | ``` 72 | 73 | If the optional `help_message` attribute is specified, `help greet` will also display it: 74 | 75 | ```txt 76 | irb(main):001> help greet 77 | Greets the user with the given name. 78 | 79 | Usage: greet 80 | ``` 81 | 82 | ## Helper methods 83 | 84 | Helper methods are designed to be used as Ruby methods, such as `my_helper(arg, kwarg: val).foo`. 85 | 86 | The main use case of helper methods is to provide shortcuts for users, providing quick and easy access to 87 | frequently used operations or components within the IRB session. For example, a helper method might simplify 88 | the process of fetching and displaying specific configuration settings or data structures that would otherwise 89 | require multiple steps to access. 90 | 91 | ### Example 92 | 93 | ```rb 94 | # This only loads the minimum components required to define and register a helper method. 95 | # It does not load the entire IRB, nor does it initialize it. 96 | require "irb/helper_method" 97 | 98 | class MyHelper < IRB::HelperMethod::Base 99 | description "This is a test helper" 100 | 101 | def execute(arg, kwarg:) 102 | "arg: #{arg}, kwarg: #{kwarg}" 103 | end 104 | end 105 | 106 | IRB::HelperMethod.register(:my_helper, MyHelper) 107 | ``` 108 | 109 | As long as the above code is loaded before the IRB session is started, such as in a loaded library or a user's `.irbrc` file, `my_helper` will be accessible to the user. 110 | 111 | ```txt 112 | irb(main):001> my_helper("foo", kwarg: "bar").upcase 113 | => "ARG: FOO, KWARG: BAR" 114 | ``` 115 | 116 | The registered helper methods will also be listed in the help message's `Helper methods` section: 117 | 118 | ```txt 119 | Helper methods 120 | conf Returns the current context. 121 | my_helper This is a test helper 122 | ``` 123 | -------------------------------------------------------------------------------- /doc/irb/irb-tools.rd.ja: -------------------------------------------------------------------------------- 1 | irb関連おまけコマンドとライブラリ 2 | $Release Version: 0.7.1 $ 3 | $Revision$ 4 | by Keiju ISHITSUKA(Nihon Rational Co.,Ltd.) 5 | 6 | =begin 7 | 8 | :コマンド: 9 | * rtags -- ruby tags command 10 | 11 | :関数ライブラリ: 12 | * xmp -- irb version of gotoken xmp-function 13 | 14 | :クラスライブラリ: 15 | * frame.rb -- frame tracer 16 | * completion.rb -- irb completor 17 | 18 | = rtags 19 | 20 | rtagsはemacs及びvi用の, TAGファイルをつくるコマンドです. 21 | 22 | == 使い方 23 | 24 | rtags [-vi] file.... 25 | 26 | カレントディレクトリにemacs用のTAGSファイルができます. -viオプションを 27 | つけた時にはvi用のtagsファイルを作成します. 28 | 29 | emacsの場合, 通常のetags.elがそのまま使えます. 検索可能なのは, 30 | 31 | * クラス 32 | * メソッド 33 | * 特異メソッド 34 | * alias 35 | * attrで宣言されたアクセサ(パラメータがシンボルか文字列リテラルに限る) 36 | * attr_XXXで宣言されたアクセサ(パラメータがシンボルか文字列リテラルに限る) 37 | 38 | です. 39 | 40 | Cなどで使っているのと違うのは, コンプリーションに関する部分で, 41 | 42 | 関数名は, 43 | 44 | 関数名( 45 | 46 | クラスは, 47 | 48 | ::クラス名::....::クラス名 49 | 50 | メソッドは, 51 | 52 | ::クラス名::....::クラス名#メソッド名 53 | 54 | 特異メソッド(クラスメソッド)は 55 | 56 | ::クラス名::....::クラス名.メソッド名 57 | 58 | でコンプリーションを行なうところです. 59 | 60 | = xmp.rb 61 | 62 | ごとけんxmpの上位互換バージョンです. ただ, 非常に重いのでごとけんxmpで 63 | は対応できない時に, 使用すると良いでしょう. 64 | 65 | == 使い方 66 | 67 | === 関数として使う. 68 | 69 | require "irb/xmp" 70 | xmp <1 77 | foo 78 | ==>1 79 | 80 | === XMPインスタンスを用いる. 81 | 82 | この場合は, XMPがコンテキスト情報を持つので, 変数の値などを保持してい 83 | ます. 84 | 85 | require "irb/xmp" 86 | xmp = XMP.new 87 | xmp.puts <1 97 | foo 98 | ==>1 99 | foo 100 | ==>1 101 | 102 | == コンテキストに関して 103 | 104 | XMPメソッド群のコンテキストは, 呼び出す前のコンテキストで評価されます. 105 | 明示的にコンテキストを指定するとそのコンテキストで評価します. 106 | 107 | 例: 108 | 109 | xmp "foo", an_binding 110 | 111 | :注: 112 | マルチスレッドには対応していません. 113 | 114 | = frame.rb 115 | 現在実行中のフレーム情報を取り扱うためのクラスです. 116 | 117 | * IRB::Frame.top(n = 0) 118 | 上からn番目のコンテキストを取り出します. nは0が最上位になります. 119 | * IRB::Frame.bottom(n = 0) 120 | 下からn番目のコンテキストを取り出します. nは0が最下位になります. 121 | * IRB::Frame.sender 122 | センダになっているオブジェクトを取り出します. センダとは, そのメソッ 123 | ドを呼び出した側のselfのことです. 124 | 125 | :注: 126 | set_trace_funcを用いてRubyの実行をトレースしています. マルチスレッドに 127 | は対応していません. 128 | 129 | = completion.rb 130 | irbのcompletion機能を提供するものです. 131 | 132 | == 使い方 133 | 134 | % irb -r irb/completion 135 | 136 | とするか, ~/.irbrc 中に 137 | 138 | require "irb/completion" 139 | 140 | を入れてください. irb実行中に require "irb/completion" してもよいです. 141 | 142 | irb実行中に (TAB) を押すとコンプレーションします. 143 | 144 | トップレベルで(TAB)を押すとすべての構文要素, クラス, メソッドの候補がで 145 | ます. 候補が唯一ならば完全に補完します. 146 | 147 | irb(main):001:0> in 148 | in inspect instance_eval 149 | include install_alias_method instance_of? 150 | initialize install_aliases instance_variables 151 | irb(main):001:0> inspect 152 | "main" 153 | irb(main):002:0> foo = Object.new 154 | # 155 | 156 | ((|変数名.|))の後に(TAB)を押すと, そのオブジェクトのメソッド一覧がでま 157 | す. 158 | 159 | irb(main):003:0> foo. 160 | foo.== foo.frozen? foo.protected_methods 161 | foo.=== foo.hash foo.public_methods 162 | foo.=~ foo.id foo.respond_to? 163 | foo.__id__ foo.inspect foo.send 164 | foo.__send__ foo.instance_eval foo.singleton_methods 165 | foo.class foo.instance_of? foo.taint 166 | foo.clone foo.instance_variables foo.tainted? 167 | foo.display foo.is_a? foo.to_a 168 | foo.dup foo.kind_of? foo.to_s 169 | foo.eql? foo.method foo.type 170 | foo.equal? foo.methods foo.untaint 171 | foo.extend foo.nil? 172 | foo.freeze foo.private_methods 173 | 174 | =end 175 | 176 | % Begin Emacs Environment 177 | % Local Variables: 178 | % mode: text 179 | % comment-column: 0 180 | % comment-start: "%" 181 | % comment-end: "\n" 182 | % End: 183 | % 184 | 185 | -------------------------------------------------------------------------------- /exe/irb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # irb.rb - interactive ruby 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | require "irb" 8 | 9 | IRB.start(__FILE__) 10 | -------------------------------------------------------------------------------- /irb.gemspec: -------------------------------------------------------------------------------- 1 | begin 2 | require_relative "lib/irb/version" 3 | rescue LoadError 4 | # for Ruby core repository 5 | require_relative "version" 6 | end 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "irb" 10 | spec.version = IRB::VERSION 11 | spec.authors = ["aycabta", "Keiju ISHITSUKA"] 12 | spec.email = ["aycabta@gmail.com", "keiju@ruby-lang.org"] 13 | 14 | spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} 15 | spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} 16 | spec.homepage = "https://github.com/ruby/irb" 17 | spec.licenses = ["Ruby", "BSD-2-Clause"] 18 | 19 | spec.metadata["homepage_uri"] = spec.homepage 20 | spec.metadata["source_code_uri"] = spec.homepage 21 | spec.metadata["documentation_uri"] = "https://ruby.github.io/irb/" 22 | spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" 23 | 24 | spec.files = [ 25 | "Gemfile", 26 | "LICENSE.txt", 27 | "README.md", 28 | "Rakefile", 29 | "bin/console", 30 | "bin/setup", 31 | "doc/irb/irb-tools.rd.ja", 32 | "doc/irb/irb.rd.ja", 33 | "exe/irb", 34 | "irb.gemspec", 35 | "man/irb.1", 36 | ] + Dir.chdir(File.expand_path('..', __FILE__)) do 37 | Dir.glob("lib/**/*").map {|f| f unless File.directory?(f) }.compact 38 | end 39 | spec.bindir = "exe" 40 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 41 | spec.require_paths = ["lib"] 42 | 43 | spec.required_ruby_version = Gem::Requirement.new(">= 2.7") 44 | 45 | spec.add_dependency "reline", ">= 0.4.2" 46 | spec.add_dependency "rdoc", ">= 4.0.0" 47 | spec.add_dependency "pp", ">= 0.6.0" 48 | end 49 | -------------------------------------------------------------------------------- /lib/.document: -------------------------------------------------------------------------------- 1 | irb.rb 2 | irb/context.rb 3 | irb/command/base.rb 4 | -------------------------------------------------------------------------------- /lib/irb/.document: -------------------------------------------------------------------------------- 1 | **/*.rb 2 | -------------------------------------------------------------------------------- /lib/irb/cmd/nop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is just a placeholder for backward-compatibility. 4 | # Please require 'irb' and inherit your command from `IRB::Command::Base` instead. 5 | -------------------------------------------------------------------------------- /lib/irb/color_printer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'pp' 3 | require_relative 'color' 4 | 5 | module IRB 6 | class ColorPrinter < ::PP 7 | class << self 8 | def pp(obj, out = $>, width = screen_width, colorize: true) 9 | q = ColorPrinter.new(out, width, colorize: colorize) 10 | q.guard_inspect_key {q.pp obj} 11 | q.flush 12 | out << "\n" 13 | end 14 | 15 | private 16 | 17 | def screen_width 18 | Reline.get_screen_size.last 19 | rescue Errno::EINVAL # in `winsize': Invalid argument - 20 | 79 21 | end 22 | end 23 | 24 | def initialize(out, width, colorize: true) 25 | @colorize = colorize 26 | 27 | super(out, width) 28 | end 29 | 30 | def pp(obj) 31 | if String === obj 32 | # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" 33 | text(obj.inspect) 34 | else 35 | super 36 | end 37 | end 38 | 39 | def text(str, width = nil) 40 | unless str.is_a?(String) 41 | str = str.inspect 42 | end 43 | width ||= str.length 44 | 45 | case str 46 | when '' 47 | when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ 48 | super(str, width) 49 | when /\A#' 50 | super(@colorize ? Color.colorize(str, [:GREEN]) : str, width) 51 | else 52 | super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/irb/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/command.rb - irb command 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | require_relative "command/base" 8 | 9 | module IRB # :nodoc: 10 | module Command 11 | @commands = {} 12 | 13 | class << self 14 | attr_reader :commands 15 | 16 | # Registers a command with the given name. 17 | # Aliasing is intentionally not supported at the moment. 18 | def register(name, command_class) 19 | @commands[name.to_sym] = [command_class, []] 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/irb/command/backtrace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Backtrace < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(pre_cmds: "backtrace #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # nop.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB 8 | module Command 9 | class CommandArgumentError < StandardError; end # :nodoc: 10 | 11 | class << self 12 | def extract_ruby_args(*args, **kwargs) # :nodoc: 13 | throw :EXTRACT_RUBY_ARGS, [args, kwargs] 14 | end 15 | end 16 | 17 | class Base 18 | class << self 19 | def category(category = nil) 20 | @category = category if category 21 | @category || "No category" 22 | end 23 | 24 | def description(description = nil) 25 | @description = description if description 26 | @description || "No description provided." 27 | end 28 | 29 | def help_message(help_message = nil) 30 | @help_message = help_message if help_message 31 | @help_message 32 | end 33 | 34 | def execute(irb_context, arg) 35 | new(irb_context).execute(arg) 36 | rescue CommandArgumentError => e 37 | puts e.message 38 | end 39 | 40 | private 41 | 42 | def highlight(text) 43 | Color.colorize(text, [:BOLD, :BLUE]) 44 | end 45 | end 46 | 47 | def initialize(irb_context) 48 | @irb_context = irb_context 49 | end 50 | 51 | attr_reader :irb_context 52 | 53 | def execute(arg) 54 | #nop 55 | end 56 | end 57 | 58 | Nop = Base # :nodoc: 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/irb/command/break.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Break < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(pre_cmds: "break #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/catch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Catch < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(pre_cmds: "catch #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/cd.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | class CD < Base 6 | category "Workspace" 7 | description "Move into the given object or leave the current context." 8 | 9 | help_message(<<~HELP) 10 | Usage: cd ([target]|..) 11 | 12 | IRB uses a stack of workspaces to keep track of context(s), with `pushws` and `popws` commands to manipulate the stack. 13 | The `cd` command is an attempt to simplify the operation and will be subject to change. 14 | 15 | When given: 16 | - an object, cd will use that object as the new context by pushing it onto the workspace stack. 17 | - "..", cd will leave the current context by popping the top workspace off the stack. 18 | - no arguments, cd will move to the top workspace on the stack by popping off all workspaces. 19 | 20 | Examples: 21 | 22 | cd Foo 23 | cd Foo.new 24 | cd @ivar 25 | cd .. 26 | cd 27 | HELP 28 | 29 | def execute(arg) 30 | case arg 31 | when ".." 32 | irb_context.pop_workspace 33 | when "" 34 | # TODO: decide what workspace commands should be kept, and underlying APIs should look like, 35 | # and perhaps add a new API to clear the workspace stack. 36 | prev_workspace = irb_context.pop_workspace 37 | while prev_workspace 38 | prev_workspace = irb_context.pop_workspace 39 | end 40 | else 41 | begin 42 | obj = eval(arg, irb_context.workspace.binding) 43 | irb_context.push_workspace(obj) 44 | rescue StandardError => e 45 | warn "Error: #{e}" 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/irb/command/chws.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # change-ws.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | require_relative "../ext/change-ws" 7 | 8 | module IRB 9 | # :stopdoc: 10 | 11 | module Command 12 | 13 | class CurrentWorkingWorkspace < Base 14 | category "Workspace" 15 | description "Show the current workspace." 16 | 17 | def execute(_arg) 18 | puts "Current workspace: #{irb_context.main}" 19 | end 20 | end 21 | 22 | class ChangeWorkspace < Base 23 | category "Workspace" 24 | description "Change the current workspace to an object." 25 | 26 | def execute(arg) 27 | if arg.empty? 28 | irb_context.change_workspace 29 | else 30 | obj = eval(arg, irb_context.workspace.binding) 31 | irb_context.change_workspace(obj) 32 | end 33 | 34 | puts "Current workspace: #{irb_context.main}" 35 | end 36 | end 37 | end 38 | 39 | # :startdoc: 40 | end 41 | -------------------------------------------------------------------------------- /lib/irb/command/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | class Context < Base 6 | category "IRB" 7 | description "Displays current configuration." 8 | 9 | def execute(_arg) 10 | # This command just displays the configuration. 11 | # Modifying the configuration is achieved by sending a message to IRB.conf. 12 | Pager.page_content(IRB.CurrentContext.inspect) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/irb/command/continue.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Continue < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(do_cmds: "continue #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/copy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | class Copy < Base 6 | category "Misc" 7 | description "Copy expression output to clipboard" 8 | 9 | help_message(<<~HELP) 10 | Usage: copy ([expression]) 11 | 12 | When given: 13 | - an expression, copy the inspect result of the expression to the clipboard. 14 | - no arguments, copy the last evaluated result (`_`) to the clipboard. 15 | 16 | Examples: 17 | 18 | copy Foo.new 19 | copy User.all.to_a 20 | copy 21 | HELP 22 | 23 | def execute(arg) 24 | # Copy last value if no expression was supplied 25 | arg = '_' if arg.to_s.strip.empty? 26 | 27 | value = irb_context.workspace.binding.eval(arg) 28 | output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp 29 | 30 | if clipboard_available? 31 | copy_to_clipboard(output) 32 | else 33 | warn "System clipboard not found" 34 | end 35 | rescue StandardError => e 36 | warn "Error: #{e}" 37 | end 38 | 39 | private 40 | 41 | def copy_to_clipboard(text) 42 | IO.popen(clipboard_program, 'w') do |io| 43 | io.write(text) 44 | end 45 | 46 | raise IOError.new("Copying to clipboard failed") unless $? == 0 47 | 48 | puts "Copied to system clipboard" 49 | rescue Errno::ENOENT => e 50 | warn e.message 51 | warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?" 52 | end 53 | 54 | def clipboard_program 55 | @clipboard_program ||= if IRB.conf[:COPY_COMMAND] 56 | IRB.conf[:COPY_COMMAND] 57 | elsif executable?("pbcopy") 58 | "pbcopy" 59 | elsif executable?("xclip") 60 | "xclip -selection clipboard" 61 | end 62 | end 63 | 64 | def executable?(command) 65 | system("which #{command} > /dev/null 2>&1") 66 | end 67 | 68 | def clipboard_available? 69 | !!clipboard_program 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/irb/command/debug.rb: -------------------------------------------------------------------------------- 1 | require_relative "../debug" 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class Debug < Base 8 | category "Debugging" 9 | description "Start the debugger of debug.gem." 10 | 11 | def execute(_arg) 12 | execute_debug_command 13 | end 14 | 15 | def execute_debug_command(pre_cmds: nil, do_cmds: nil) 16 | pre_cmds = pre_cmds&.rstrip 17 | do_cmds = do_cmds&.rstrip 18 | 19 | if irb_context.with_debugger 20 | # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. 21 | if cmd = pre_cmds || do_cmds 22 | throw :IRB_EXIT, cmd 23 | else 24 | puts "IRB is already running with a debug session." 25 | return 26 | end 27 | else 28 | # If IRB is not running with a debug session yet, then: 29 | # 1. Check if the debugging command is run from a `binding.irb` call. 30 | # 2. If so, try setting up the debug gem. 31 | # 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command. 32 | # 4. Exit the current Irb#run call via `throw :IRB_EXIT`. 33 | # 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command. 34 | unless irb_context.from_binding? 35 | puts "Debugging commands are only available when IRB is started with binding.irb" 36 | return 37 | end 38 | 39 | if IRB.respond_to?(:JobManager) 40 | warn "Can't start the debugger when IRB is running in a multi-IRB session." 41 | return 42 | end 43 | 44 | unless IRB::Debug.setup(irb_context.irb) 45 | puts <<~MSG 46 | You need to install the debug gem before using this command. 47 | If you use `bundle exec`, please add `gem "debug"` into your Gemfile. 48 | MSG 49 | return 50 | end 51 | 52 | IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds) 53 | 54 | # exit current Irb#run call 55 | throw :IRB_EXIT 56 | end 57 | end 58 | end 59 | 60 | class DebugCommand < Debug 61 | class << self 62 | def category 63 | "Debugging" 64 | end 65 | 66 | def description 67 | command_name = self.name.split("::").last.downcase 68 | "Start the debugger of debug.gem and run its `#{command_name}` command." 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/irb/command/delete.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Delete < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(pre_cmds: "delete #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/disable_irb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class DisableIrb < Base 8 | category "IRB" 9 | description "Disable binding.irb." 10 | 11 | def execute(*) 12 | ::Binding.define_method(:irb) {} 13 | IRB.irb_exit 14 | end 15 | end 16 | end 17 | 18 | # :startdoc: 19 | end 20 | -------------------------------------------------------------------------------- /lib/irb/command/edit.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | 3 | require_relative "../color" 4 | require_relative "../source_finder" 5 | 6 | module IRB 7 | # :stopdoc: 8 | 9 | module Command 10 | class Edit < Base 11 | include RubyArgsExtractor 12 | 13 | category "Misc" 14 | description 'Open a file or source location.' 15 | help_message <<~HELP_MESSAGE 16 | Usage: edit [FILE or constant or method signature] 17 | 18 | Open a file in the editor specified in #{highlight('ENV["VISUAL"]')} or #{highlight('ENV["EDITOR"]')} 19 | 20 | - If no arguments are provided, IRB will attempt to open the file the current context was defined in. 21 | - If FILE is provided, IRB will open the file. 22 | - If a constant or method signature is provided, IRB will attempt to locate the source file and open it. 23 | 24 | Examples: 25 | 26 | edit 27 | edit foo.rb 28 | edit Foo 29 | edit Foo#bar 30 | HELP_MESSAGE 31 | 32 | def execute(arg) 33 | # Accept string literal for backward compatibility 34 | path = unwrap_string_literal(arg) 35 | 36 | if path.nil? 37 | path = @irb_context.irb_path 38 | elsif !File.exist?(path) 39 | source = SourceFinder.new(@irb_context).find_source(path) 40 | 41 | if source&.file_exist? && !source.binary_file? 42 | path = source.file 43 | end 44 | end 45 | 46 | unless File.exist?(path) 47 | puts "Can not find file: #{path}" 48 | return 49 | end 50 | 51 | if editor = (ENV['VISUAL'] || ENV['EDITOR']) 52 | puts "command: '#{editor}'" 53 | puts " path: #{path}" 54 | system(*Shellwords.split(editor), path) 55 | else 56 | puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']" 57 | end 58 | end 59 | end 60 | end 61 | 62 | # :startdoc: 63 | end 64 | -------------------------------------------------------------------------------- /lib/irb/command/exit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class Exit < Base 8 | category "IRB" 9 | description "Exit the current irb session." 10 | 11 | def execute(_arg) 12 | IRB.irb_exit 13 | end 14 | end 15 | end 16 | 17 | # :startdoc: 18 | end 19 | -------------------------------------------------------------------------------- /lib/irb/command/finish.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Finish < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(do_cmds: "finish #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/force_exit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class ForceExit < Base 8 | category "IRB" 9 | description "Exit the current process." 10 | 11 | def execute(_arg) 12 | throw :IRB_EXIT, true 13 | end 14 | end 15 | end 16 | 17 | # :startdoc: 18 | end 19 | -------------------------------------------------------------------------------- /lib/irb/command/help.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | class Help < Base 6 | category "Help" 7 | description "List all available commands. Use `help ` to get information about a specific command." 8 | 9 | def execute(command_name) 10 | content = 11 | if command_name.empty? 12 | help_message 13 | else 14 | if command_class = Command.load_command(command_name) 15 | command_class.help_message || command_class.description 16 | else 17 | "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" 18 | end 19 | end 20 | Pager.page_content(content) 21 | end 22 | 23 | private 24 | 25 | def help_message 26 | commands_info = IRB::Command.all_commands_info 27 | helper_methods_info = IRB::HelperMethod.all_helper_methods_info 28 | commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } 29 | commands_grouped_by_categories["Helper methods"] = helper_methods_info 30 | 31 | if irb_context.with_debugger 32 | # Remove the original "Debugging" category 33 | commands_grouped_by_categories.delete("Debugging") 34 | end 35 | 36 | longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max 37 | 38 | output = StringIO.new 39 | 40 | help_cmds = commands_grouped_by_categories.delete("Help") 41 | no_category_cmds = commands_grouped_by_categories.delete("No category") 42 | aliases = irb_context.instance_variable_get(:@command_aliases).map do |alias_name, target| 43 | { display_name: alias_name, description: "Alias for `#{target}`" } 44 | end 45 | 46 | # Display help commands first 47 | add_category_to_output("Help", help_cmds, output, longest_cmd_name_length) 48 | 49 | # Display the rest of the commands grouped by categories 50 | commands_grouped_by_categories.each do |category, cmds| 51 | add_category_to_output(category, cmds, output, longest_cmd_name_length) 52 | end 53 | 54 | # Display commands without a category 55 | if no_category_cmds 56 | add_category_to_output("No category", no_category_cmds, output, longest_cmd_name_length) 57 | end 58 | 59 | # Display aliases 60 | add_category_to_output("Aliases", aliases, output, longest_cmd_name_length) 61 | 62 | # Append the debugger help at the end 63 | if irb_context.with_debugger 64 | # Add "Debugging (from debug.gem)" category as title 65 | add_category_to_output("Debugging (from debug.gem)", [], output, longest_cmd_name_length) 66 | output.puts DEBUGGER__.help 67 | end 68 | 69 | output.string 70 | end 71 | 72 | def add_category_to_output(category, cmds, output, longest_cmd_name_length) 73 | output.puts Color.colorize(category, [:BOLD]) 74 | 75 | cmds.each do |cmd| 76 | output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}" 77 | end 78 | 79 | output.puts 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/irb/command/history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "stringio" 4 | 5 | require_relative "../pager" 6 | 7 | module IRB 8 | # :stopdoc: 9 | 10 | module Command 11 | class History < Base 12 | category "IRB" 13 | description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." 14 | 15 | def execute(arg) 16 | 17 | if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\z/)) 18 | grep = Regexp.new(match[:grep]) 19 | end 20 | 21 | formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| 22 | next if grep && !input.match?(grep) 23 | 24 | header = "#{index}: " 25 | 26 | first_line, *other_lines = input.split("\n") 27 | first_line = "#{header}#{first_line}" 28 | 29 | truncated_lines = other_lines.slice!(1..) # Show 1 additional line (2 total) 30 | other_lines << "..." if truncated_lines&.any? 31 | 32 | other_lines.map! do |line| 33 | " " * header.length + line 34 | end 35 | 36 | [first_line, *other_lines].join("\n") + "\n" 37 | end 38 | 39 | Pager.page_content(formatted_inputs.join) 40 | end 41 | end 42 | end 43 | 44 | # :startdoc: 45 | end 46 | -------------------------------------------------------------------------------- /lib/irb/command/info.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Info < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(pre_cmds: "info #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/internal_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | # Internal use only, for default command's backward compatibility. 6 | module RubyArgsExtractor # :nodoc: 7 | def unwrap_string_literal(str) 8 | return if str.empty? 9 | 10 | sexp = Ripper.sexp(str) 11 | if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal 12 | @irb_context.workspace.binding.eval(str).to_s 13 | else 14 | str 15 | end 16 | end 17 | 18 | def ruby_args(arg) 19 | # Use throw and catch to handle arg that includes `;` 20 | # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] 21 | catch(:EXTRACT_RUBY_ARGS) do 22 | @irb_context.workspace.binding.eval "::IRB::Command.extract_ruby_args #{arg}" 23 | end || [[], {}] 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/irb/command/irb_info.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class IrbInfo < Base 8 | category "IRB" 9 | description "Show information about IRB." 10 | 11 | def execute(_arg) 12 | str = "Ruby version: #{RUBY_VERSION}\n" 13 | str += "IRB version: #{IRB.version}\n" 14 | str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" 15 | str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n" 16 | rc_files = IRB.irbrc_files 17 | str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any? 18 | str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" 19 | str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? 20 | str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? 21 | str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" 22 | if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ 23 | codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') 24 | str += "Code page: #{codepage}\n" 25 | end 26 | puts str 27 | nil 28 | end 29 | end 30 | end 31 | 32 | # :startdoc: 33 | end 34 | -------------------------------------------------------------------------------- /lib/irb/command/load.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # load.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | require_relative "../ext/loader" 7 | 8 | module IRB 9 | # :stopdoc: 10 | 11 | module Command 12 | class LoaderCommand < Base 13 | include RubyArgsExtractor 14 | include IrbLoader 15 | 16 | def raise_cmd_argument_error 17 | raise CommandArgumentError.new("Please specify the file name.") 18 | end 19 | end 20 | 21 | class Load < LoaderCommand 22 | category "IRB" 23 | description "Load a Ruby file." 24 | 25 | def execute(arg) 26 | args, kwargs = ruby_args(arg) 27 | execute_internal(*args, **kwargs) 28 | end 29 | 30 | def execute_internal(file_name = nil, priv = nil) 31 | raise_cmd_argument_error unless file_name 32 | irb_load(file_name, priv) 33 | end 34 | end 35 | 36 | class Require < LoaderCommand 37 | category "IRB" 38 | description "Require a Ruby file." 39 | 40 | def execute(arg) 41 | args, kwargs = ruby_args(arg) 42 | execute_internal(*args, **kwargs) 43 | end 44 | 45 | def execute_internal(file_name = nil) 46 | raise_cmd_argument_error unless file_name 47 | 48 | rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") 49 | return false if $".find{|f| f =~ rex} 50 | 51 | case file_name 52 | when /\.rb$/ 53 | begin 54 | if irb_load(file_name) 55 | $".push file_name 56 | return true 57 | end 58 | rescue LoadError 59 | end 60 | when /\.(so|o|sl)$/ 61 | return ruby_require(file_name) 62 | end 63 | 64 | begin 65 | irb_load(f = file_name + ".rb") 66 | $".push f 67 | return true 68 | rescue LoadError 69 | return ruby_require(file_name) 70 | end 71 | end 72 | end 73 | 74 | class Source < LoaderCommand 75 | category "IRB" 76 | description "Loads a given file in the current session." 77 | 78 | def execute(arg) 79 | args, kwargs = ruby_args(arg) 80 | execute_internal(*args, **kwargs) 81 | end 82 | 83 | def execute_internal(file_name = nil) 84 | raise_cmd_argument_error unless file_name 85 | 86 | source_file(file_name) 87 | end 88 | end 89 | end 90 | # :startdoc: 91 | end 92 | -------------------------------------------------------------------------------- /lib/irb/command/ls.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "reline" 4 | require "stringio" 5 | 6 | require_relative "../pager" 7 | require_relative "../color" 8 | 9 | module IRB 10 | # :stopdoc: 11 | 12 | module Command 13 | class Ls < Base 14 | class EvaluationError < StandardError; end 15 | 16 | category "Context" 17 | description "Show methods, constants, and variables." 18 | 19 | help_message <<~HELP_MESSAGE 20 | Usage: ls [obj] [-g [query]] 21 | 22 | -g [query] Filter the output with a query. 23 | HELP_MESSAGE 24 | 25 | def evaluate(code) 26 | @irb_context.workspace.binding.eval(code) 27 | rescue Exception => e 28 | puts "#{e.class}: #{e.message}" 29 | raise EvaluationError 30 | end 31 | 32 | def execute(arg) 33 | if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) 34 | target = match[:target] 35 | grep = Regexp.new(match[:grep]) 36 | elsif match = arg.match(/\A((?.+),|)\s*grep:(?.+)/) 37 | # Legacy style `ls obj, grep: /regexp/` 38 | # Evaluation order should be eval(target) then eval(grep) 39 | target = match[:target] || '' 40 | grep_regexp_code = match[:grep] 41 | else 42 | target = arg.strip 43 | end 44 | 45 | if target.empty? 46 | obj = irb_context.workspace.main 47 | locals = irb_context.workspace.binding.local_variables 48 | else 49 | obj = evaluate(target) 50 | end 51 | 52 | if grep_regexp_code 53 | grep = evaluate(grep_regexp_code) 54 | end 55 | 56 | o = Output.new(grep: grep) 57 | 58 | klass = (obj.class == Class || obj.class == Module ? obj : obj.class) 59 | 60 | o.dump("constants", obj.constants) if obj.respond_to?(:constants) 61 | dump_methods(o, klass, obj) 62 | o.dump("instance variables", obj.instance_variables) 63 | o.dump("class variables", klass.class_variables) 64 | o.dump("locals", locals) if locals 65 | o.print_result 66 | rescue EvaluationError 67 | end 68 | 69 | def dump_methods(o, klass, obj) 70 | singleton_class = begin obj.singleton_class; rescue TypeError; nil end 71 | dumped_mods = Array.new 72 | ancestors = klass.ancestors 73 | ancestors = ancestors.reject { |c| c >= Object } if klass < Object 74 | singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class } 75 | 76 | # singleton_class' ancestors should be at the front 77 | maps = class_method_map(singleton_ancestors, dumped_mods) + class_method_map(ancestors, dumped_mods) 78 | maps.each do |mod, methods| 79 | name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" 80 | o.dump(name, methods) 81 | end 82 | end 83 | 84 | def class_method_map(classes, dumped_mods) 85 | dumped_methods = Array.new 86 | classes.map do |mod| 87 | next if dumped_mods.include? mod 88 | 89 | dumped_mods << mod 90 | 91 | methods = mod.public_instance_methods(false).select do |method| 92 | if dumped_methods.include? method 93 | false 94 | else 95 | dumped_methods << method 96 | true 97 | end 98 | end 99 | 100 | [mod, methods] 101 | end.compact 102 | end 103 | 104 | class Output 105 | MARGIN = " " 106 | 107 | def initialize(grep: nil) 108 | @grep = grep 109 | @line_width = screen_width - MARGIN.length # right padding 110 | @io = StringIO.new 111 | end 112 | 113 | def print_result 114 | Pager.page_content(@io.string) 115 | end 116 | 117 | def dump(name, strs) 118 | strs = strs.grep(@grep) if @grep 119 | strs = strs.sort 120 | return if strs.empty? 121 | 122 | # Attempt a single line 123 | @io.print "#{Color.colorize(name, [:BOLD, :BLUE])}: " 124 | if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) 125 | @io.puts strs.join(MARGIN) 126 | return 127 | end 128 | @io.puts 129 | 130 | # Dump with the largest # of columns that fits on a line 131 | cols = strs.size 132 | until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 133 | cols -= 1 134 | end 135 | widths = col_widths(strs, cols: cols) 136 | strs.each_slice(cols) do |ss| 137 | @io.puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join 138 | end 139 | end 140 | 141 | private 142 | 143 | def fits_on_line?(strs, cols:, offset: 0) 144 | width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) 145 | width <= @line_width - offset 146 | end 147 | 148 | def col_widths(strs, cols:) 149 | cols.times.map do |col| 150 | (col...strs.size).step(cols).map do |i| 151 | strs[i].length 152 | end.max 153 | end 154 | end 155 | 156 | def screen_width 157 | Reline.get_screen_size.last 158 | rescue Errno::EINVAL # in `winsize': Invalid argument - 159 | 80 160 | end 161 | end 162 | private_constant :Output 163 | end 164 | end 165 | 166 | # :startdoc: 167 | end 168 | -------------------------------------------------------------------------------- /lib/irb/command/measure.rb: -------------------------------------------------------------------------------- 1 | module IRB 2 | # :stopdoc: 3 | 4 | module Command 5 | class Measure < Base 6 | include RubyArgsExtractor 7 | 8 | category "Misc" 9 | description "`measure` enables the mode to measure processing time. `measure :off` disables it." 10 | 11 | def initialize(*args) 12 | super(*args) 13 | end 14 | 15 | def execute(arg) 16 | if arg&.match?(/^do$|^do[^\w]|^\{/) 17 | warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' 18 | return 19 | end 20 | args, kwargs = ruby_args(arg) 21 | execute_internal(*args, **kwargs) 22 | end 23 | 24 | def execute_internal(type = nil, arg = nil) 25 | # Please check IRB.init_config in lib/irb/init.rb that sets 26 | # IRB.conf[:MEASURE_PROC] to register default "measure" methods, 27 | # "measure :time" (abbreviated as "measure") and "measure :stackprof". 28 | 29 | case type 30 | when :off 31 | IRB.unset_measure_callback(arg) 32 | when :list 33 | IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val| 34 | puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '') 35 | end 36 | when :on 37 | added = IRB.set_measure_callback(arg) 38 | puts "#{added[0]} is added." if added 39 | else 40 | added = IRB.set_measure_callback(type, arg) 41 | puts "#{added[0]} is added." if added 42 | end 43 | nil 44 | end 45 | end 46 | end 47 | 48 | # :startdoc: 49 | end 50 | -------------------------------------------------------------------------------- /lib/irb/command/next.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Next < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(do_cmds: "next #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/pushws.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # change-ws.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | require_relative "../ext/workspaces" 8 | 9 | module IRB 10 | # :stopdoc: 11 | 12 | module Command 13 | class Workspaces < Base 14 | category "Workspace" 15 | description "Show workspaces." 16 | 17 | def execute(_arg) 18 | inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| 19 | truncated_inspect(ws.main) 20 | end 21 | 22 | puts "[" + inspection_resuls.join(", ") + "]" 23 | end 24 | 25 | private 26 | 27 | def truncated_inspect(obj) 28 | obj_inspection = obj.inspect 29 | 30 | if obj_inspection.size > 20 31 | obj_inspection = obj_inspection[0, 19] + "...>" 32 | end 33 | 34 | obj_inspection 35 | end 36 | end 37 | 38 | class PushWorkspace < Workspaces 39 | category "Workspace" 40 | description "Push an object to the workspace stack." 41 | 42 | def execute(arg) 43 | if arg.empty? 44 | irb_context.push_workspace 45 | else 46 | obj = eval(arg, irb_context.workspace.binding) 47 | irb_context.push_workspace(obj) 48 | end 49 | super 50 | end 51 | end 52 | 53 | class PopWorkspace < Workspaces 54 | category "Workspace" 55 | description "Pop a workspace from the workspace stack." 56 | 57 | def execute(_arg) 58 | irb_context.pop_workspace 59 | super 60 | end 61 | end 62 | end 63 | 64 | # :startdoc: 65 | end 66 | -------------------------------------------------------------------------------- /lib/irb/command/show_doc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Command 5 | class ShowDoc < Base 6 | include RubyArgsExtractor 7 | 8 | category "Context" 9 | description "Look up documentation with RI." 10 | 11 | help_message <<~HELP_MESSAGE 12 | Usage: show_doc [name] 13 | 14 | When name is provided, IRB will look up the documentation for the given name. 15 | When no name is provided, a RI session will be started. 16 | 17 | Examples: 18 | 19 | show_doc 20 | show_doc Array 21 | show_doc Array#each 22 | 23 | HELP_MESSAGE 24 | 25 | def execute(arg) 26 | # Accept string literal for backward compatibility 27 | name = unwrap_string_literal(arg) 28 | require 'rdoc/ri/driver' 29 | 30 | unless ShowDoc.const_defined?(:Ri) 31 | opts = RDoc::RI::Driver.process_args([]) 32 | ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) 33 | end 34 | 35 | if name.nil? 36 | Ri.interactive 37 | else 38 | begin 39 | Ri.display_name(name) 40 | rescue RDoc::RI::Error 41 | puts $!.message 42 | end 43 | end 44 | 45 | nil 46 | rescue LoadError, SystemExit 47 | warn "Can't display document because `rdoc` is not installed." 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/irb/command/show_source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../source_finder" 4 | require_relative "../pager" 5 | require_relative "../color" 6 | 7 | module IRB 8 | module Command 9 | class ShowSource < Base 10 | include RubyArgsExtractor 11 | 12 | category "Context" 13 | description "Show the source code of a given method, class/module, or constant." 14 | 15 | help_message <<~HELP_MESSAGE 16 | Usage: show_source [target] [-s] 17 | 18 | -s Show the super method. You can stack it like `-ss` to show the super of the super, etc. 19 | 20 | Examples: 21 | 22 | show_source Foo 23 | show_source Foo#bar 24 | show_source Foo#bar -s 25 | show_source Foo.baz 26 | show_source Foo::BAR 27 | HELP_MESSAGE 28 | 29 | def execute(arg) 30 | # Accept string literal for backward compatibility 31 | str = unwrap_string_literal(arg) 32 | unless str.is_a?(String) 33 | puts "Error: Expected a string but got #{str.inspect}" 34 | return 35 | end 36 | 37 | str, esses = str.split(" -") 38 | super_level = esses ? esses.count("s") : 0 39 | source = SourceFinder.new(@irb_context).find_source(str, super_level) 40 | 41 | if source 42 | show_source(source) 43 | elsif super_level > 0 44 | puts "Error: Couldn't locate a super definition for #{str}" 45 | else 46 | puts "Error: Couldn't locate a definition for #{str}" 47 | end 48 | nil 49 | end 50 | 51 | private 52 | 53 | def show_source(source) 54 | if source.binary_file? 55 | content = "\n#{bold('Defined in binary file')}: #{source.file}\n\n" 56 | else 57 | code = source.colorized_content || 'Source not available' 58 | content = <<~CONTENT 59 | 60 | #{bold("From")}: #{source.file}:#{source.line} 61 | 62 | #{code.chomp} 63 | 64 | CONTENT 65 | end 66 | Pager.page_content(content) 67 | end 68 | 69 | def bold(str) 70 | Color.colorize(str, [:BOLD]) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/irb/command/step.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "debug" 4 | 5 | module IRB 6 | # :stopdoc: 7 | 8 | module Command 9 | class Step < DebugCommand 10 | def execute(arg) 11 | execute_debug_command(do_cmds: "step #{arg}") 12 | end 13 | end 14 | end 15 | 16 | # :startdoc: 17 | end 18 | -------------------------------------------------------------------------------- /lib/irb/command/subirb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # multi.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB 8 | # :stopdoc: 9 | 10 | module Command 11 | class MultiIRBCommand < Base 12 | include RubyArgsExtractor 13 | 14 | private 15 | 16 | def print_deprecated_warning 17 | warn <<~MSG 18 | Multi-irb commands are deprecated and will be removed in IRB 2.0.0. Please use workspace commands instead. 19 | If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653 20 | MSG 21 | end 22 | 23 | def extend_irb_context 24 | # this extension patches IRB context like IRB.CurrentContext 25 | require_relative "../ext/multi-irb" 26 | end 27 | 28 | def print_debugger_warning 29 | warn "Multi-IRB commands are not available when the debugger is enabled." 30 | end 31 | end 32 | 33 | class IrbCommand < MultiIRBCommand 34 | category "Multi-irb (DEPRECATED)" 35 | description "Start a child IRB." 36 | 37 | def execute(arg) 38 | args, kwargs = ruby_args(arg) 39 | execute_internal(*args, **kwargs) 40 | end 41 | 42 | def execute_internal(*obj) 43 | print_deprecated_warning 44 | 45 | if irb_context.with_debugger 46 | print_debugger_warning 47 | return 48 | end 49 | 50 | extend_irb_context 51 | IRB.irb(nil, *obj) 52 | puts IRB.JobManager.inspect 53 | end 54 | end 55 | 56 | class Jobs < MultiIRBCommand 57 | category "Multi-irb (DEPRECATED)" 58 | description "List of current sessions." 59 | 60 | def execute(_arg) 61 | print_deprecated_warning 62 | 63 | if irb_context.with_debugger 64 | print_debugger_warning 65 | return 66 | end 67 | 68 | extend_irb_context 69 | puts IRB.JobManager.inspect 70 | end 71 | end 72 | 73 | class Foreground < MultiIRBCommand 74 | category "Multi-irb (DEPRECATED)" 75 | description "Switches to the session of the given number." 76 | 77 | def execute(arg) 78 | args, kwargs = ruby_args(arg) 79 | execute_internal(*args, **kwargs) 80 | end 81 | 82 | def execute_internal(key = nil) 83 | print_deprecated_warning 84 | 85 | if irb_context.with_debugger 86 | print_debugger_warning 87 | return 88 | end 89 | 90 | extend_irb_context 91 | 92 | raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key 93 | IRB.JobManager.switch(key) 94 | puts IRB.JobManager.inspect 95 | end 96 | end 97 | 98 | class Kill < MultiIRBCommand 99 | category "Multi-irb (DEPRECATED)" 100 | description "Kills the session with the given number." 101 | 102 | def execute(arg) 103 | args, kwargs = ruby_args(arg) 104 | execute_internal(*args, **kwargs) 105 | end 106 | 107 | def execute_internal(*keys) 108 | print_deprecated_warning 109 | 110 | if irb_context.with_debugger 111 | print_debugger_warning 112 | return 113 | end 114 | 115 | extend_irb_context 116 | IRB.JobManager.kill(*keys) 117 | puts IRB.JobManager.inspect 118 | end 119 | end 120 | end 121 | 122 | # :startdoc: 123 | end 124 | -------------------------------------------------------------------------------- /lib/irb/command/whereami.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | # :stopdoc: 5 | 6 | module Command 7 | class Whereami < Base 8 | category "Context" 9 | description "Show the source code around binding.irb again." 10 | 11 | def execute(_arg) 12 | code = irb_context.workspace.code_around_binding 13 | if code 14 | puts code 15 | else 16 | puts "The current context doesn't have code." 17 | end 18 | end 19 | end 20 | end 21 | 22 | # :startdoc: 23 | end 24 | -------------------------------------------------------------------------------- /lib/irb/debug.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | module Debug 5 | IRB_DIR = File.expand_path('..', __dir__) 6 | 7 | class << self 8 | def insert_debug_break(pre_cmds: nil, do_cmds: nil) 9 | options = { oneshot: true, hook_call: false } 10 | 11 | if pre_cmds || do_cmds 12 | options[:command] = ['irb', pre_cmds, do_cmds] 13 | end 14 | if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) 15 | options[:skip_src] = true 16 | end 17 | 18 | # To make debugger commands like `next` or `continue` work without asking 19 | # the user to quit IRB after that, we need to exit IRB first and then hit 20 | # a TracePoint on #debug_break. 21 | file, lineno = IRB::Irb.instance_method(:debug_break).source_location 22 | DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) 23 | end 24 | 25 | def setup(irb) 26 | # When debug session is not started at all 27 | unless defined?(DEBUGGER__::SESSION) 28 | begin 29 | require "debug/session" 30 | rescue LoadError # debug.gem is not written in Gemfile 31 | return false unless load_bundled_debug_gem 32 | end 33 | DEBUGGER__::CONFIG.set_config 34 | configure_irb_for_debugger(irb) 35 | 36 | DEBUGGER__.initialize_session{ IRB::Debug::UI.new(irb) } 37 | end 38 | 39 | # When debug session was previously started but not by IRB 40 | if defined?(DEBUGGER__::SESSION) && !irb.context.with_debugger 41 | configure_irb_for_debugger(irb) 42 | DEBUGGER__::SESSION.reset_ui(IRB::Debug::UI.new(irb)) 43 | end 44 | 45 | # Apply patches to debug gem so it skips IRB frames 46 | unless DEBUGGER__.respond_to?(:capture_frames_without_irb) 47 | DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) 48 | 49 | def DEBUGGER__.capture_frames(*args) 50 | frames = capture_frames_without_irb(*args) 51 | frames.reject! do |frame| 52 | frame.realpath&.start_with?(IRB_DIR) || frame.path == "" 53 | end 54 | frames 55 | end 56 | 57 | DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) 58 | end 59 | 60 | if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) 61 | Reline.output_modifier_proc = proc do |input, complete:| 62 | unless input.strip.empty? 63 | cmd = input[/\S+/] 64 | 65 | if !complete && DEBUGGER__.commands.key?(cmd) 66 | input = input.sub(/\n$/, " # debug command\n") 67 | end 68 | end 69 | 70 | irb.context.colorize_input(input, complete: complete) 71 | end 72 | end 73 | 74 | true 75 | end 76 | 77 | private 78 | 79 | def configure_irb_for_debugger(irb) 80 | require 'irb/debug/ui' 81 | IRB.instance_variable_set(:@debugger_irb, irb) 82 | irb.context.with_debugger = true 83 | irb.context.irb_name += ":rdbg" 84 | irb.context.io.load_history if irb.context.io.class < HistorySavingAbility 85 | end 86 | 87 | module SkipPathHelperForIRB 88 | def skip_internal_path?(path) 89 | # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved 90 | super || path.match?(IRB_DIR) || path.match?('') 91 | end 92 | end 93 | 94 | # This is used when debug.gem is not written in Gemfile. Even if it's not 95 | # installed by `bundle install`, debug.gem is installed by default because 96 | # it's a bundled gem. This method tries to activate and load that. 97 | def load_bundled_debug_gem 98 | # Discover latest debug.gem under GEM_PATH 99 | debug_gem = Gem.paths.path.flat_map { |path| Dir.glob("#{path}/gems/debug-*") }.select do |path| 100 | File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+(\w+)?\z/) 101 | end.sort_by do |path| 102 | Gem::Version.new(File.basename(path).delete_prefix('debug-')) 103 | end.last 104 | return false unless debug_gem 105 | 106 | # Discover debug/debug.so under extensions for Ruby 3.2+ 107 | ext_name = "/debug/debug.#{RbConfig::CONFIG['DLEXT']}" 108 | ext_path = Gem.paths.path.flat_map do |path| 109 | Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}#{ext_name}") 110 | end.first 111 | 112 | # Attempt to forcibly load the bundled gem 113 | if ext_path 114 | $LOAD_PATH << ext_path.delete_suffix(ext_name) 115 | end 116 | $LOAD_PATH << "#{debug_gem}/lib" 117 | begin 118 | require "debug/session" 119 | puts "Loaded #{File.basename(debug_gem)}" 120 | true 121 | rescue LoadError 122 | false 123 | end 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/irb/debug/ui.rb: -------------------------------------------------------------------------------- 1 | require 'io/console/size' 2 | require 'debug/console' 3 | 4 | module IRB 5 | module Debug 6 | class UI < DEBUGGER__::UI_Base 7 | def initialize(irb) 8 | @irb = irb 9 | end 10 | 11 | def remote? 12 | false 13 | end 14 | 15 | def activate session, on_fork: false 16 | end 17 | 18 | def deactivate 19 | end 20 | 21 | def width 22 | if (w = IO.console_size[1]) == 0 # for tests PTY 23 | 80 24 | else 25 | w 26 | end 27 | end 28 | 29 | def quit n 30 | yield 31 | exit n 32 | end 33 | 34 | def ask prompt 35 | setup_interrupt do 36 | print prompt 37 | ($stdin.gets || '').strip 38 | end 39 | end 40 | 41 | def puts str = nil 42 | case str 43 | when Array 44 | str.each{|line| 45 | $stdout.puts line.chomp 46 | } 47 | when String 48 | Pager.page_content(str, retain_content: true) 49 | when nil 50 | $stdout.puts 51 | end 52 | end 53 | 54 | def readline _ 55 | setup_interrupt do 56 | tc = DEBUGGER__::SESSION.instance_variable_get(:@tc) 57 | cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING) 58 | 59 | case cmd 60 | when nil # when user types C-d 61 | "continue" 62 | else 63 | cmd 64 | end 65 | end 66 | end 67 | 68 | def setup_interrupt 69 | DEBUGGER__::SESSION.intercept_trap_sigint false do 70 | current_thread = Thread.current # should be session_server thread 71 | 72 | prev_handler = trap(:INT){ 73 | current_thread.raise Interrupt 74 | } 75 | 76 | yield 77 | ensure 78 | trap(:INT, prev_handler) 79 | end 80 | end 81 | 82 | def after_fork_parent 83 | parent_pid = Process.pid 84 | 85 | at_exit{ 86 | DEBUGGER__::SESSION.intercept_trap_sigint_end 87 | trap(:SIGINT, :IGNORE) 88 | 89 | if Process.pid == parent_pid 90 | # only check child process from its parent 91 | begin 92 | # wait for all child processes to keep terminal 93 | Process.waitpid 94 | rescue Errno::ESRCH, Errno::ECHILD 95 | end 96 | end 97 | } 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/irb/easter-egg.rb: -------------------------------------------------------------------------------- 1 | require "reline" 2 | 3 | module IRB 4 | class << self 5 | class Vec 6 | def initialize(x, y, z) 7 | @x, @y, @z = x, y, z 8 | end 9 | 10 | attr_reader :x, :y, :z 11 | 12 | def sub(other) 13 | Vec.new(@x - other.x, @y - other.y, @z - other.z) 14 | end 15 | 16 | def dot(other) 17 | @x*other.x + @y*other.y + @z*other.z 18 | end 19 | 20 | def cross(other) 21 | ox, oy, oz = other.x, other.y, other.z 22 | Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox) 23 | end 24 | 25 | def normalize 26 | r = Math.sqrt(self.dot(self)) 27 | Vec.new(@x / r, @y / r, @z / r) 28 | end 29 | end 30 | 31 | class Canvas 32 | def initialize((h, w)) 33 | @data = (0..h-2).map { [0] * w } 34 | @scale = [w / 2.0, h-2].min 35 | @center = Complex(w / 2, h-2) 36 | end 37 | 38 | def line((x1, y1), (x2, y2)) 39 | p1 = Complex(x1, y1) / 2 * @scale + @center 40 | p2 = Complex(x2, y2) / 2 * @scale + @center 41 | line0(p1, p2) 42 | end 43 | 44 | private def line0(p1, p2) 45 | mid = (p1 + p2) / 2 46 | if (p1 - p2).abs < 1 47 | x, y = mid.rect 48 | @data[y / 2][x] |= (y % 2 > 1 ? 2 : 1) 49 | else 50 | line0(p1, mid) 51 | line0(p2, mid) 52 | end 53 | end 54 | 55 | def draw 56 | @data.each {|row| row.fill(0) } 57 | yield 58 | @data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n") 59 | end 60 | end 61 | 62 | class RubyModel 63 | def initialize 64 | @faces = init_ruby_model 65 | end 66 | 67 | def init_ruby_model 68 | cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) } 69 | middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) } 70 | bottom_vertex = Vec.new(0, 0, -2) 71 | 72 | faces = [cap_vertices] 73 | 6.times do |j| 74 | i = j-1 75 | faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]] 76 | faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]] 77 | faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]] 78 | end 79 | 80 | faces 81 | end 82 | 83 | def render_frame(i) 84 | angle = i / 10.0 85 | dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize 86 | dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0) 87 | up = dir.cross(dir2) 88 | nm = dir.cross(up) 89 | @faces.each do |vertices| 90 | v0, v1, v2, = vertices 91 | if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0 92 | points = vertices.map {|p| [nm.dot(p), up.dot(p)] } 93 | (points + [points[0]]).each_cons(2) do |p1, p2| 94 | yield p1, p2 95 | end 96 | end 97 | end 98 | end 99 | end 100 | 101 | private def easter_egg_logo(type) 102 | @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8') 103 | .split(/TYPE: ([A-Z_]+)\n/)[1..] 104 | .each_slice(2) 105 | .to_h 106 | @easter_egg_logos[type.to_s.upcase] 107 | end 108 | 109 | private def easter_egg(type = nil) 110 | print "\e[?1049h" 111 | type ||= [:logo, :dancing].sample 112 | case type 113 | when :logo 114 | Pager.page do |io| 115 | logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large 116 | io.write easter_egg_logo(logo_type) 117 | STDIN.raw { STDIN.getc } if io == STDOUT 118 | end 119 | when :dancing 120 | STDOUT.cooked do 121 | interrupted = false 122 | prev_trap = trap("SIGINT") { interrupted = true } 123 | canvas = Canvas.new(Reline.get_screen_size) 124 | Reline::IOGate.set_winch_handler do 125 | canvas = Canvas.new(Reline.get_screen_size) 126 | end 127 | ruby_model = RubyModel.new 128 | print "\e[?25l" # hide cursor 129 | 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later 130 | buff = canvas.draw do 131 | ruby_model.render_frame(i) do |p1, p2| 132 | canvas.line(p1, p2) 133 | end 134 | end 135 | buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m" 136 | print "\e[H" + buff 137 | sleep 0.05 138 | break if interrupted 139 | end 140 | rescue Interrupt 141 | ensure 142 | print "\e[?25h" # show cursor 143 | trap("SIGINT", prev_trap) 144 | end 145 | end 146 | ensure 147 | print "\e[0m\e[?1049l" 148 | end 149 | end 150 | end 151 | 152 | IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__ 153 | -------------------------------------------------------------------------------- /lib/irb/ext/change-ws.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/ext/cb.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB # :nodoc: 8 | class Context 9 | 10 | # Inherited from +TOPLEVEL_BINDING+. 11 | def home_workspace 12 | if defined? @home_workspace 13 | @home_workspace 14 | else 15 | @home_workspace = workspace 16 | end 17 | end 18 | 19 | # Changes the current workspace to given object or binding. 20 | # 21 | # If the optional argument is omitted, the workspace will be 22 | # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main 23 | # object, IRB.conf[:MAIN_CONTEXT] when irb was initialized. 24 | # 25 | # See IRB::WorkSpace.new for more information. 26 | def change_workspace(*_main) 27 | if _main.empty? 28 | replace_workspace(home_workspace) 29 | return main 30 | end 31 | 32 | workspace = WorkSpace.new(_main[0]) 33 | replace_workspace(workspace) 34 | workspace.load_helper_methods_to_main 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/irb/ext/eval_history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # history.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB # :nodoc: 8 | 9 | class Context 10 | 11 | NOPRINTING_IVARS.push "@eval_history_values" 12 | 13 | # See #set_last_value 14 | alias _set_last_value set_last_value 15 | 16 | def set_last_value(value) 17 | _set_last_value(value) 18 | 19 | if defined?(@eval_history) && @eval_history 20 | @eval_history_values.push @line_no, @last_value 21 | workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}" 22 | end 23 | 24 | @last_value 25 | end 26 | 27 | remove_method :eval_history= if method_defined?(:eval_history=) 28 | # The command result history limit. This method is not available until 29 | # #eval_history= was called with non-nil value (directly or via 30 | # setting IRB.conf[:EVAL_HISTORY] in .irbrc). 31 | attr_reader :eval_history 32 | # Sets command result history limit. Default value is set from 33 | # IRB.conf[:EVAL_HISTORY]. 34 | # 35 | # +no+ is an Integer or +nil+. 36 | # 37 | # Returns +no+ of history items if greater than 0. 38 | # 39 | # If +no+ is 0, the number of history items is unlimited. 40 | # 41 | # If +no+ is +nil+, execution result history isn't used (default). 42 | # 43 | # EvalHistory values are available via __ variable, see 44 | # IRB::EvalHistory. 45 | def eval_history=(no) 46 | if no 47 | if defined?(@eval_history) && @eval_history 48 | @eval_history_values.size(no) 49 | else 50 | @eval_history_values = EvalHistory.new(no) 51 | IRB.conf[:__TMP__EHV__] = @eval_history_values 52 | workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]") 53 | IRB.conf.delete(:__TMP_EHV__) 54 | end 55 | else 56 | @eval_history_values = nil 57 | end 58 | @eval_history = no 59 | end 60 | end 61 | 62 | # Represents history of results of previously evaluated commands. 63 | # 64 | # Available via __ variable, only if IRB.conf[:EVAL_HISTORY] 65 | # or IRB::CurrentContext().eval_history is non-nil integer value 66 | # (by default it is +nil+). 67 | # 68 | # Example (in `irb`): 69 | # 70 | # # Initialize history 71 | # IRB::CurrentContext().eval_history = 10 72 | # # => 10 73 | # 74 | # # Perform some commands... 75 | # 1 + 2 76 | # # => 3 77 | # puts 'x' 78 | # # x 79 | # # => nil 80 | # raise RuntimeError 81 | # # ...error raised 82 | # 83 | # # Inspect history (format is " ": 84 | # __ 85 | # # => 1 10 86 | # # 2 3 87 | # # 3 nil 88 | # 89 | # __[1] 90 | # # => 10 91 | # 92 | class EvalHistory 93 | 94 | def initialize(size = 16) # :nodoc: 95 | @size = size 96 | @contents = [] 97 | end 98 | 99 | def size(size) # :nodoc: 100 | if size != 0 && size < @size 101 | @contents = @contents[@size - size .. @size] 102 | end 103 | @size = size 104 | end 105 | 106 | # Get one item of the content (both positive and negative indexes work). 107 | def [](idx) 108 | begin 109 | if idx >= 0 110 | @contents.find{|no, val| no == idx}[1] 111 | else 112 | @contents[idx][1] 113 | end 114 | rescue NameError 115 | nil 116 | end 117 | end 118 | 119 | def push(no, val) # :nodoc: 120 | @contents.push [no, val] 121 | @contents.shift if @size != 0 && @contents.size > @size 122 | end 123 | 124 | alias real_inspect inspect 125 | 126 | def inspect # :nodoc: 127 | if @contents.empty? 128 | return real_inspect 129 | end 130 | 131 | unless (last = @contents.pop)[1].equal?(self) 132 | @contents.push last 133 | last = nil 134 | end 135 | str = @contents.collect{|no, val| 136 | if val.equal?(self) 137 | "#{no} ...self-history..." 138 | else 139 | "#{no} #{val.inspect}" 140 | end 141 | }.join("\n") 142 | if str == "" 143 | str = "Empty." 144 | end 145 | @contents.push last if last 146 | str 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/irb/ext/loader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # loader.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB # :nodoc: 8 | # Raised in the event of an exception in a file loaded from an Irb session 9 | class LoadAbort < Exception;end 10 | 11 | # Provides a few commands for loading files within an irb session. 12 | # 13 | # See ExtendCommandBundle for more information. 14 | module IrbLoader 15 | 16 | alias ruby_load load 17 | alias ruby_require require 18 | 19 | # Loads the given file similarly to Kernel#load 20 | def irb_load(fn, priv = nil) 21 | path = search_file_from_ruby_path(fn) 22 | raise LoadError, "No such file to load -- #{fn}" unless path 23 | 24 | load_file(path, priv) 25 | end 26 | 27 | def search_file_from_ruby_path(fn) # :nodoc: 28 | if File.absolute_path?(fn) 29 | return fn if File.exist?(fn) 30 | return nil 31 | end 32 | 33 | for path in $: 34 | if File.exist?(f = File.join(path, fn)) 35 | return f 36 | end 37 | end 38 | return nil 39 | end 40 | 41 | # Loads a given file in the current session and displays the source lines 42 | # 43 | # See Irb#suspend_input_method for more information. 44 | def source_file(path) 45 | irb = irb_context.irb 46 | irb.suspend_name(path, File.basename(path)) do 47 | FileInputMethod.open(path) do |io| 48 | irb.suspend_input_method(io) do 49 | |back_io| 50 | irb.signal_status(:IN_LOAD) do 51 | if back_io.kind_of?(FileInputMethod) 52 | irb.eval_input 53 | else 54 | begin 55 | irb.eval_input 56 | rescue LoadAbort 57 | print "load abort!!\n" 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | 66 | # Loads the given file in the current session's context and evaluates it. 67 | # 68 | # See Irb#suspend_input_method for more information. 69 | def load_file(path, priv = nil) 70 | irb = irb_context.irb 71 | irb.suspend_name(path, File.basename(path)) do 72 | 73 | if priv 74 | ws = WorkSpace.new(Module.new) 75 | else 76 | ws = WorkSpace.new 77 | end 78 | irb.suspend_workspace(ws) do 79 | FileInputMethod.open(path) do |io| 80 | irb.suspend_input_method(io) do 81 | |back_io| 82 | irb.signal_status(:IN_LOAD) do 83 | if back_io.kind_of?(FileInputMethod) 84 | irb.eval_input 85 | else 86 | begin 87 | irb.eval_input 88 | rescue LoadAbort 89 | print "load abort!!\n" 90 | end 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | 99 | def old # :nodoc: 100 | back_io = @io 101 | back_path = irb_path 102 | back_name = @irb_name 103 | back_scanner = @irb.scanner 104 | begin 105 | @io = FileInputMethod.new(path) 106 | @irb_name = File.basename(path) 107 | self.irb_path = path 108 | @irb.signal_status(:IN_LOAD) do 109 | if back_io.kind_of?(FileInputMethod) 110 | @irb.eval_input 111 | else 112 | begin 113 | @irb.eval_input 114 | rescue LoadAbort 115 | print "load abort!!\n" 116 | end 117 | end 118 | end 119 | ensure 120 | @io = back_io 121 | @irb_name = back_name 122 | self.irb_path = back_path 123 | @irb.scanner = back_scanner 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/irb/ext/tracer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/lib/tracer.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | # Loading the gem "tracer" will cause it to extend IRB commands with: 7 | # https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb 8 | begin 9 | require "tracer" 10 | rescue LoadError 11 | $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." 12 | return # This is about to disable loading below 13 | end 14 | 15 | module IRB 16 | class CallTracer < ::CallTracer 17 | IRB_DIR = File.expand_path('../..', __dir__) 18 | 19 | def skip?(tp) 20 | super || tp.path.match?(IRB_DIR) || tp.path.match?('') 21 | end 22 | end 23 | class WorkSpace 24 | alias __evaluate__ evaluate 25 | # Evaluate the context of this workspace and use the Tracer library to 26 | # output the exact lines of code are being executed in chronological order. 27 | # 28 | # See https://github.com/ruby/tracer for more information. 29 | def evaluate(statements, file = __FILE__, line = __LINE__) 30 | if IRB.conf[:USE_TRACER] == true 31 | CallTracer.new(colorize: Color.colorable?).start do 32 | __evaluate__(statements, file, line) 33 | end 34 | else 35 | __evaluate__(statements, file, line) 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/irb/ext/use-loader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # use-loader.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | require_relative "../command/load" 8 | require_relative "loader" 9 | 10 | class Object 11 | alias __original__load__IRB_use_loader__ load 12 | alias __original__require__IRB_use_loader__ require 13 | end 14 | 15 | module IRB 16 | module ExtendCommandBundle 17 | remove_method :irb_load if method_defined?(:irb_load) 18 | # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load 19 | def irb_load(*opts, &b) 20 | Command::Load.execute(irb_context, *opts, &b) 21 | end 22 | remove_method :irb_require if method_defined?(:irb_require) 23 | # Loads the given file similarly to Kernel#require 24 | def irb_require(*opts, &b) 25 | Command::Require.execute(irb_context, *opts, &b) 26 | end 27 | end 28 | 29 | class Context 30 | 31 | IRB.conf[:USE_LOADER] = false 32 | 33 | # Returns whether +irb+'s own file reader method is used by 34 | # +load+/+require+ or not. 35 | # 36 | # This mode is globally affected (irb-wide). 37 | def use_loader 38 | IRB.conf[:USE_LOADER] 39 | end 40 | 41 | alias use_loader? use_loader 42 | 43 | remove_method :use_loader= if method_defined?(:use_loader=) 44 | # Sets IRB.conf[:USE_LOADER] 45 | # 46 | # See #use_loader for more information. 47 | def use_loader=(opt) 48 | 49 | if IRB.conf[:USE_LOADER] != opt 50 | IRB.conf[:USE_LOADER] = opt 51 | if opt 52 | (class< 1 17 | # swap the top two workspaces 18 | previous_workspace, current_workspace = @workspace_stack.pop(2) 19 | @workspace_stack.push current_workspace, previous_workspace 20 | end 21 | else 22 | new_workspace = WorkSpace.new(workspace.binding, _main[0]) 23 | @workspace_stack.push new_workspace 24 | new_workspace.load_helper_methods_to_main 25 | end 26 | end 27 | 28 | # Removes the last element from the current #workspaces stack and returns 29 | # it, or +nil+ if the current workspace stack is empty. 30 | # 31 | # Also, see #push_workspace. 32 | def pop_workspace 33 | @workspace_stack.pop if @workspace_stack.size > 1 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/irb/frame.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # frame.rb - 4 | # by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) 5 | # 6 | 7 | module IRB 8 | class Frame 9 | class FrameOverflow < StandardError 10 | def initialize 11 | super("frame overflow") 12 | end 13 | end 14 | class FrameUnderflow < StandardError 15 | def initialize 16 | super("frame underflow") 17 | end 18 | end 19 | 20 | # Default number of stack frames 21 | INIT_STACK_TIMES = 3 22 | # Default number of frames offset 23 | CALL_STACK_OFFSET = 3 24 | 25 | # Creates a new stack frame 26 | def initialize 27 | @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES 28 | end 29 | 30 | # Used by Kernel#set_trace_func to register each event in the call stack 31 | def trace_func(event, file, line, id, binding) 32 | case event 33 | when 'call', 'class' 34 | @frames.push binding 35 | when 'return', 'end' 36 | @frames.pop 37 | end 38 | end 39 | 40 | # Returns the +n+ number of frames on the call stack from the last frame 41 | # initialized. 42 | # 43 | # Raises FrameUnderflow if there are no frames in the given stack range. 44 | def top(n = 0) 45 | bind = @frames[-(n + CALL_STACK_OFFSET)] 46 | fail FrameUnderflow unless bind 47 | bind 48 | end 49 | 50 | # Returns the +n+ number of frames on the call stack from the first frame 51 | # initialized. 52 | # 53 | # Raises FrameOverflow if there are no frames in the given stack range. 54 | def bottom(n = 0) 55 | bind = @frames[n] 56 | fail FrameOverflow unless bind 57 | bind 58 | end 59 | 60 | # Convenience method for Frame#bottom 61 | def Frame.bottom(n = 0) 62 | @backtrace.bottom(n) 63 | end 64 | 65 | # Convenience method for Frame#top 66 | def Frame.top(n = 0) 67 | @backtrace.top(n) 68 | end 69 | 70 | # Returns the binding context of the caller from the last frame initialized 71 | def Frame.sender 72 | eval "self", @backtrace.top 73 | end 74 | 75 | @backtrace = Frame.new 76 | set_trace_func proc{|event, file, line, id, binding, klass| 77 | @backtrace.trace_func(event, file, line, id, binding) 78 | } 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/irb/help.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/help.rb - print usage module 4 | # by Keiju ISHITSUKA(keiju@ishitsuka.com) 5 | # 6 | 7 | module IRB 8 | # Outputs the irb help message, see IRB@Command-Line+Options. 9 | def IRB.print_usage # :nodoc: 10 | lc = IRB.conf[:LC_MESSAGES] 11 | path = lc.find("irb/help-message") 12 | space_line = false 13 | File.open(path){|f| 14 | f.each_line do |l| 15 | if /^\s*$/ =~ l 16 | lc.puts l unless space_line 17 | space_line = true 18 | next 19 | end 20 | space_line = false 21 | 22 | l.sub!(/#.*$/, "") 23 | next if /^\s*$/ =~ l 24 | lc.puts l 25 | end 26 | } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/irb/helper_method.rb: -------------------------------------------------------------------------------- 1 | require_relative "helper_method/base" 2 | 3 | module IRB 4 | module HelperMethod 5 | @helper_methods = {} 6 | 7 | class << self 8 | attr_reader :helper_methods 9 | 10 | def register(name, helper_class) 11 | @helper_methods[name] = helper_class 12 | 13 | if defined?(HelpersContainer) 14 | HelpersContainer.install_helper_methods 15 | end 16 | end 17 | 18 | def all_helper_methods_info 19 | @helper_methods.map do |name, helper_class| 20 | { display_name: name, description: helper_class.description } 21 | end 22 | end 23 | end 24 | 25 | # Default helper_methods 26 | require_relative "helper_method/conf" 27 | register(:conf, HelperMethod::Conf) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/irb/helper_method/base.rb: -------------------------------------------------------------------------------- 1 | require "singleton" 2 | 3 | module IRB 4 | module HelperMethod 5 | class Base 6 | include Singleton 7 | 8 | class << self 9 | def description(description = nil) 10 | @description = description if description 11 | @description 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/irb/helper_method/conf.rb: -------------------------------------------------------------------------------- 1 | module IRB 2 | module HelperMethod 3 | class Conf < Base 4 | description "Returns the current IRB context." 5 | 6 | def execute 7 | IRB.CurrentContext 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/irb/history.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | module IRB 4 | module History 5 | DEFAULT_ENTRY_LIMIT = 1000 6 | 7 | class << self 8 | # Integer representation of IRB.conf[:HISTORY_FILE]. 9 | def save_history 10 | return 0 if IRB.conf[:SAVE_HISTORY] == false 11 | return DEFAULT_ENTRY_LIMIT if IRB.conf[:SAVE_HISTORY] == true 12 | IRB.conf[:SAVE_HISTORY].to_i 13 | end 14 | 15 | def save_history? 16 | !save_history.zero? 17 | end 18 | 19 | def infinite? 20 | save_history.negative? 21 | end 22 | 23 | # Might be nil when HOME and XDG_CONFIG_HOME are not available. 24 | def history_file 25 | if (history_file = IRB.conf[:HISTORY_FILE]) 26 | File.expand_path(history_file) 27 | else 28 | IRB.rc_file("_history") 29 | end 30 | end 31 | end 32 | end 33 | 34 | module HistorySavingAbility # :nodoc: 35 | def support_history_saving? 36 | true 37 | end 38 | 39 | def reset_history_counter 40 | @loaded_history_lines = self.class::HISTORY.size 41 | end 42 | 43 | def load_history 44 | history_file = History.history_file 45 | return unless File.exist?(history_file.to_s) 46 | 47 | history = self.class::HISTORY 48 | 49 | File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| 50 | f.each { |l| 51 | l = l.chomp 52 | if self.class == RelineInputMethod and history.last&.end_with?("\\") 53 | history.last.delete_suffix!("\\") 54 | history.last << "\n" << l 55 | else 56 | history << l 57 | end 58 | } 59 | end 60 | @loaded_history_lines = history.size 61 | @loaded_history_mtime = File.mtime(history_file) 62 | end 63 | 64 | def save_history 65 | return unless History.save_history? 66 | return unless (history_file = History.history_file) 67 | unless ensure_history_file_writable(history_file) 68 | warn <<~WARN 69 | Can't write history to #{History.history_file.inspect} due to insufficient permissions. 70 | Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. 71 | WARN 72 | return 73 | end 74 | 75 | history = self.class::HISTORY.to_a 76 | 77 | if File.exist?(history_file) && 78 | File.mtime(history_file) != @loaded_history_mtime 79 | history = history[@loaded_history_lines..-1] if @loaded_history_lines 80 | append_history = true 81 | end 82 | 83 | File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| 84 | hist = history.map { |l| l.scrub.split("\n").join("\\\n") } 85 | 86 | unless append_history || History.infinite? 87 | # Check size before slicing because array.last(huge_number) raises RangeError. 88 | hist = hist.last(History.save_history) if hist.size > History.save_history 89 | end 90 | 91 | f.puts(hist) 92 | end 93 | end 94 | 95 | private 96 | 97 | # Returns boolean whether writing to +history_file+ will be possible. 98 | # Permissions of already existing +history_file+ are changed to 99 | # owner-only-readable if necessary [BUG #7694]. 100 | def ensure_history_file_writable(history_file) 101 | history_file = Pathname.new(history_file) 102 | 103 | return false unless history_file.dirname.writable? 104 | return true unless history_file.exist? 105 | 106 | begin 107 | if history_file.stat.mode & 0o66 != 0 108 | history_file.chmod 0o600 109 | end 110 | true 111 | rescue Errno::EPERM # no permissions 112 | false 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/irb/inspector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/inspector.rb - inspect methods 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB # :nodoc: 8 | 9 | # Convenience method to create a new Inspector, using the given +inspect+ 10 | # proc, and optional +init+ proc and passes them to Inspector.new 11 | # 12 | # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" }) 13 | # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! # 14 | # irb(main):001:0> "what?" #=> omg! what? 15 | # 16 | def IRB::Inspector(inspect, init = nil) 17 | Inspector.new(inspect, init) 18 | end 19 | 20 | # An irb inspector 21 | # 22 | # In order to create your own custom inspector there are two things you 23 | # should be aware of: 24 | # 25 | # Inspector uses #inspect_value, or +inspect_proc+, for output of return values. 26 | # 27 | # This also allows for an optional #init+, or +init_proc+, which is called 28 | # when the inspector is activated. 29 | # 30 | # Knowing this, you can create a rudimentary inspector as follows: 31 | # 32 | # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" }) 33 | # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! # 34 | # irb(main):001:0> "what?" #=> omg! what? 35 | # 36 | class Inspector 37 | KERNEL_INSPECT = Object.instance_method(:inspect) 38 | # Default inspectors available to irb, this includes: 39 | # 40 | # +:pp+:: Using Kernel#pretty_inspect 41 | # +:yaml+:: Using YAML.dump 42 | # +:marshal+:: Using Marshal.dump 43 | INSPECTORS = {} 44 | 45 | class << self 46 | # Determines the inspector to use where +inspector+ is one of the keys passed 47 | # during inspector definition. 48 | def keys_with_inspector(inspector) 49 | INSPECTORS.filter_map {|k, v| k if v == inspector} 50 | end 51 | 52 | # Example 53 | # 54 | # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} 55 | # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} 56 | # Inspector.def_inspector(key, inspector) 57 | # Inspector.def_inspector([key1,...], inspector) 58 | def def_inspector(key, arg=nil, &block) 59 | if block_given? 60 | inspector = IRB::Inspector(block, arg) 61 | else 62 | inspector = arg 63 | end 64 | 65 | case key 66 | when Array 67 | for k in key 68 | def_inspector(k, inspector) 69 | end 70 | when Symbol 71 | INSPECTORS[key] = inspector 72 | INSPECTORS[key.to_s] = inspector 73 | when String 74 | INSPECTORS[key] = inspector 75 | INSPECTORS[key.intern] = inspector 76 | else 77 | INSPECTORS[key] = inspector 78 | end 79 | end 80 | end 81 | 82 | # Creates a new inspector object, using the given +inspect_proc+ when 83 | # output return values in irb. 84 | def initialize(inspect_proc, init_proc = nil) 85 | @init = init_proc 86 | @inspect = inspect_proc 87 | end 88 | 89 | # Proc to call when the inspector is activated, good for requiring 90 | # dependent libraries. 91 | def init 92 | @init.call if @init 93 | end 94 | 95 | def support_stream_output? 96 | second_parameter_type = @inspect.parameters[1]&.first 97 | second_parameter_type == :req || second_parameter_type == :opt 98 | end 99 | 100 | # Proc to call when the input is evaluated and output in irb. 101 | def inspect_value(v, output, colorize: true) 102 | support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize) 103 | rescue => e 104 | puts "An error occurred when inspecting the object: #{e.inspect}" 105 | 106 | begin 107 | puts "Result of Kernel#inspect: #{KERNEL_INSPECT.bind_call(v)}" 108 | '' 109 | rescue => e 110 | puts "An error occurred when running Kernel#inspect: #{e.inspect}" 111 | puts e.backtrace.join("\n") 112 | '' 113 | end 114 | end 115 | end 116 | 117 | Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} 118 | Inspector.def_inspector([:p, :inspect]){|v, colorize: true| 119 | Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v)) 120 | } 121 | Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true| 122 | IRB::ColorPrinter.pp(v, output, colorize: colorize) 123 | } 124 | Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| 125 | begin 126 | YAML.dump(v) 127 | rescue 128 | puts "(can't dump yaml. use inspect)" 129 | v.inspect 130 | end 131 | } 132 | 133 | Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v| 134 | Marshal.dump(v) 135 | } 136 | end 137 | -------------------------------------------------------------------------------- /lib/irb/lc/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/lc/error.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB 8 | # :stopdoc: 9 | 10 | class UnrecognizedSwitch < StandardError 11 | def initialize(val) 12 | super("Unrecognized switch: #{val}") 13 | end 14 | end 15 | class CantReturnToNormalMode < StandardError 16 | def initialize 17 | super("Can't return to normal mode.") 18 | end 19 | end 20 | class IllegalParameter < StandardError 21 | def initialize(val) 22 | super("Invalid parameter(#{val}).") 23 | end 24 | end 25 | class IrbAlreadyDead < StandardError 26 | def initialize 27 | super("Irb is already dead.") 28 | end 29 | end 30 | class IrbSwitchedToCurrentThread < StandardError 31 | def initialize 32 | super("Switched to current thread.") 33 | end 34 | end 35 | class NoSuchJob < StandardError 36 | def initialize(val) 37 | super("No such job(#{val}).") 38 | end 39 | end 40 | class CantChangeBinding < StandardError 41 | def initialize(val) 42 | super("Can't change binding to (#{val}).") 43 | end 44 | end 45 | class UndefinedPromptMode < StandardError 46 | def initialize(val) 47 | super("Undefined prompt mode(#{val}).") 48 | end 49 | end 50 | 51 | # :startdoc: 52 | end 53 | -------------------------------------------------------------------------------- /lib/irb/lc/help-message: -------------------------------------------------------------------------------- 1 | Usage: irb.rb [options] [programfile] [arguments] 2 | -f Don't initialize from configuration file. 3 | -d Set $DEBUG and $VERBOSE to true (same as 'ruby -d'). 4 | -r load-module Require load-module (same as 'ruby -r'). 5 | -I path Specify $LOAD_PATH directory (same as 'ruby -I'). 6 | -U Set external and internal encodings to UTF-8. 7 | -E ex[:in] Set default external (ex) and internal (in) encodings 8 | (same as 'ruby -E'). 9 | -w Suppress warnings (same as 'ruby -w'). 10 | -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose 11 | (same as 'ruby -W'). 12 | --context-mode n Set n[0-4] to method to create Binding Object, 13 | when new workspace was created. 14 | --extra-doc-dir Add an extra doc dir for the doc dialog. 15 | --echo Show result (default). 16 | --noecho Don't show result. 17 | --echo-on-assignment 18 | Show result on assignment. 19 | --noecho-on-assignment 20 | Don't show result on assignment. 21 | --truncate-echo-on-assignment 22 | Show truncated result on assignment (default). 23 | --inspect Use 'inspect' for output. 24 | --noinspect Don't use 'inspect' for output. 25 | --no-pager Don't use pager. 26 | --multiline Use multiline editor module (default). 27 | --nomultiline Don't use multiline editor module. 28 | --singleline Use single line editor module. 29 | --nosingleline Don't use single line editor module (default). 30 | --colorize Use color-highlighting (default). 31 | --nocolorize Don't use color-highlighting. 32 | --autocomplete Use auto-completion (default). 33 | --noautocomplete Don't use auto-completion. 34 | --regexp-completor 35 | Use regexp based completion (default). 36 | --type-completor Use type based completion. 37 | --prompt prompt-mode, --prompt-mode prompt-mode 38 | Set prompt mode. Pre-defined prompt modes are: 39 | 'default', 'classic', 'simple', 'inf-ruby', 'xmp', 'null'. 40 | --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. 41 | Suppresses --multiline and --singleline. 42 | --sample-book-mode, --simple-prompt 43 | Set prompt mode to 'simple'. 44 | --noprompt Don't output prompt. 45 | --script Script mode (default, treat first argument as script) 46 | --noscript No script mode (leave arguments in argv) 47 | --single-irb Share self with sub-irb. 48 | --tracer Show stack trace for each command. 49 | --back-trace-limit n[=16] 50 | Display backtrace top n and bottom n. 51 | --verbose Show details. 52 | --noverbose Don't show details. 53 | -v, --version Print the version of irb. 54 | -h, --help Print help. 55 | -- Separate options of irb from the list of command-line args. 56 | -------------------------------------------------------------------------------- /lib/irb/lc/ja/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/lc/ja/error.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB 8 | # :stopdoc: 9 | 10 | class UnrecognizedSwitch < StandardError 11 | def initialize(val) 12 | super("スイッチ(#{val})が分りません") 13 | end 14 | end 15 | class CantReturnToNormalMode < StandardError 16 | def initialize 17 | super("Normalモードに戻れません.") 18 | end 19 | end 20 | class IllegalParameter < StandardError 21 | def initialize(val) 22 | super("パラメータ(#{val})が間違っています.") 23 | end 24 | end 25 | class IrbAlreadyDead < StandardError 26 | def initialize 27 | super("Irbは既に死んでいます.") 28 | end 29 | end 30 | class IrbSwitchedToCurrentThread < StandardError 31 | def initialize 32 | super("カレントスレッドに切り替わりました.") 33 | end 34 | end 35 | class NoSuchJob < StandardError 36 | def initialize(val) 37 | super("そのようなジョブ(#{val})はありません.") 38 | end 39 | end 40 | class CantChangeBinding < StandardError 41 | def initialize(val) 42 | super("バインディング(#{val})に変更できません.") 43 | end 44 | end 45 | class UndefinedPromptMode < StandardError 46 | def initialize(val) 47 | super("プロンプトモード(#{val})は定義されていません.") 48 | end 49 | end 50 | 51 | # :startdoc: 52 | end 53 | # vim:fileencoding=utf-8 54 | -------------------------------------------------------------------------------- /lib/irb/lc/ja/help-message: -------------------------------------------------------------------------------- 1 | Usage: irb.rb [options] [programfile] [arguments] 2 | -f ~/.irbrc を読み込まない. 3 | -d $DEBUG をtrueにする(ruby -d と同じ) 4 | -r load-module ruby -r と同じ. 5 | -I path $LOAD_PATH に path を追加する. 6 | -U ruby -U と同じ. 7 | -E enc ruby -E と同じ. 8 | -w ruby -w と同じ. 9 | -W[level=2] ruby -W と同じ. 10 | --context-mode n 新しいワークスペースを作成した時に関連する Binding 11 | オブジェクトの作成方法を 0 から 4 のいずれかに設定する. 12 | --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. 13 | --echo 実行結果を表示する(デフォルト). 14 | --noecho 実行結果を表示しない. 15 | --echo-on-assignment 16 | 代入結果を表示する. 17 | --noecho-on-assignment 18 | 代入結果を表示しない. 19 | --truncate-echo-on-assignment 20 | truncateされた代入結果を表示する(デフォルト). 21 | --inspect 結果出力にinspectを用いる. 22 | --noinspect 結果出力にinspectを用いない. 23 | --no-pager ページャを使用しない. 24 | --multiline マルチラインエディタを利用する. 25 | --nomultiline マルチラインエディタを利用しない. 26 | --singleline シングルラインエディタを利用する. 27 | --nosingleline シングルラインエディタを利用しない. 28 | --colorize 色付けを利用する. 29 | --nocolorize 色付けを利用しない. 30 | --autocomplete オートコンプリートを利用する. 31 | --noautocomplete オートコンプリートを利用しない. 32 | --regexp-completor 33 | 補完に正規表現を利用する. 34 | --type-completor 補完に型情報を利用する. 35 | --prompt prompt-mode/--prompt-mode prompt-mode 36 | プロンプトモードを切り替える. 37 | 現在定義されているプロンプトモードは, 38 | default, classic, simple, inf-ruby, xmp, null. 39 | --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 40 | に指定がない限り, シングルラインエディタとマルチラ 41 | インエディタは使わなくなる. 42 | --sample-book-mode/--simple-prompt 43 | 非常にシンプルなプロンプトを用いるモードです. 44 | --noprompt プロンプト表示を行なわない. 45 | --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) 46 | --noscript 引数をargvとして扱う. 47 | --single-irb irb 中で self を実行して得られるオブジェクトをサ 48 | ブ irb と共有する. 49 | --tracer コマンド実行時にトレースを行なう. 50 | --back-trace-limit n 51 | バックトレース表示をバックトレースの頭から n, 後ろ 52 | からnだけ行なう. デフォルトは16 53 | 54 | --verbose 詳細なメッセージを出力する. 55 | --noverbose 詳細なメッセージを出力しない(デフォルト). 56 | -v, --version irbのバージョンを表示する. 57 | -h, --help irb のヘルプを表示する. 58 | -- 以降のコマンドライン引数をオプションとして扱わない. 59 | -------------------------------------------------------------------------------- /lib/irb/locale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/locale.rb - internationalization module 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB # :nodoc: 8 | class Locale 9 | 10 | LOCALE_NAME_RE = %r[ 11 | (?[[:alpha:]]{2,3}) 12 | (?:_ (?[[:alpha:]]{2,3}) )? 13 | (?:\. (?[^@]+) )? 14 | (?:@ (?.*) )? 15 | ]x 16 | LOCALE_DIR = "/lc/" 17 | 18 | LEGACY_ENCODING_ALIAS_MAP = { 19 | 'ujis' => Encoding::EUC_JP, 20 | 'euc' => Encoding::EUC_JP 21 | } 22 | 23 | @@loaded = [] 24 | 25 | def initialize(locale = nil) 26 | @override_encoding = nil 27 | @lang = @territory = @encoding_name = @modifier = nil 28 | @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" 29 | if m = LOCALE_NAME_RE.match(@locale) 30 | @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier] 31 | 32 | if @encoding_name 33 | if @encoding = LEGACY_ENCODING_ALIAS_MAP[@encoding_name] 34 | warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1) 35 | else 36 | @encoding = Encoding.find(@encoding_name) rescue nil 37 | end 38 | end 39 | end 40 | @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) 41 | end 42 | 43 | attr_reader :lang, :territory, :modifier 44 | 45 | def encoding 46 | @override_encoding || @encoding 47 | end 48 | 49 | def String(mes) 50 | mes = super(mes) 51 | if encoding 52 | mes.encode(encoding, undef: :replace) 53 | else 54 | mes 55 | end 56 | end 57 | 58 | def format(*opts) 59 | String(super(*opts)) 60 | end 61 | 62 | def gets(*rs) 63 | String(super(*rs)) 64 | end 65 | 66 | def readline(*rs) 67 | String(super(*rs)) 68 | end 69 | 70 | def print(*opts) 71 | ary = opts.collect{|opt| String(opt)} 72 | super(*ary) 73 | end 74 | 75 | def printf(*opts) 76 | s = format(*opts) 77 | print s 78 | end 79 | 80 | def puts(*opts) 81 | ary = opts.collect{|opt| String(opt)} 82 | super(*ary) 83 | end 84 | 85 | def load(file) 86 | found = find(file) 87 | if found 88 | unless @@loaded.include?(found) 89 | @@loaded << found # cache 90 | Kernel.load(found) 91 | end 92 | else 93 | raise LoadError, "No such file to load -- #{file}" 94 | end 95 | end 96 | 97 | def find(file, paths = $:) 98 | dir = File.dirname(file) 99 | dir = "" if dir == "." 100 | base = File.basename(file) 101 | 102 | if dir.start_with?('/') 103 | return each_localized_path(dir, base).find{|full_path| File.readable? full_path} 104 | else 105 | return search_file(paths, dir, base) 106 | end 107 | end 108 | 109 | # @param paths load paths in which IRB find a localized file. 110 | # @param dir directory 111 | # @param file basename to be localized 112 | # 113 | # typically, for the parameters and a in paths, it searches 114 | # /// 115 | def search_file(lib_paths, dir, file) 116 | each_localized_path(dir, file) do |lc_path| 117 | lib_paths.each do |libpath| 118 | full_path = File.join(libpath, lc_path) 119 | return full_path if File.readable?(full_path) 120 | end 121 | redo if defined?(Gem) and Gem.try_activate(lc_path) 122 | end 123 | nil 124 | end 125 | 126 | def each_localized_path(dir, file) 127 | return enum_for(:each_localized_path) unless block_given? 128 | each_sublocale do |lc| 129 | yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file) 130 | end 131 | end 132 | 133 | def each_sublocale 134 | if @lang 135 | if @territory 136 | if @encoding_name 137 | yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier 138 | yield "#{@lang}_#{@territory}.#{@encoding_name}" 139 | end 140 | yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier 141 | yield "#{@lang}_#{@territory}" 142 | end 143 | if @encoding_name 144 | yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier 145 | yield "#{@lang}.#{@encoding_name}" 146 | end 147 | yield "#{@lang}@#{@modifier}" if @modifier 148 | yield "#{@lang}" 149 | end 150 | yield nil 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/irb/output-method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # output-method.rb - output methods used by irb 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | module IRB 8 | # An abstract output class for IO in irb. This is mainly used internally by 9 | # IRB::Notifier. You can define your own output method to use with Irb.new, 10 | # or Context.new 11 | class OutputMethod 12 | # Open this method to implement your own output method, raises a 13 | # NotImplementedError if you don't define #print in your own class. 14 | def print(*opts) 15 | raise NotImplementedError 16 | end 17 | 18 | # Prints the given +opts+, with a newline delimiter. 19 | def printn(*opts) 20 | print opts.join(" "), "\n" 21 | end 22 | 23 | # Extends IO#printf to format the given +opts+ for Kernel#sprintf using 24 | # #parse_printf_format 25 | def printf(format, *opts) 26 | if /(%*)%I/ =~ format 27 | format, opts = parse_printf_format(format, opts) 28 | end 29 | print sprintf(format, *opts) 30 | end 31 | 32 | # Returns an array of the given +format+ and +opts+ to be used by 33 | # Kernel#sprintf, if there was a successful Regexp match in the given 34 | # +format+ from #printf 35 | # 36 | # % 37 | # [#0- +] 38 | # (\*|\*[1-9][0-9]*\$|[1-9][0-9]*) 39 | # .(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)? 40 | # #(hh|h|l|ll|L|q|j|z|t) 41 | # [diouxXeEfgGcsb%] 42 | def parse_printf_format(format, opts) 43 | return format, opts if $1.size % 2 == 1 44 | end 45 | 46 | # Calls #print on each element in the given +objs+, followed by a newline 47 | # character. 48 | def puts(*objs) 49 | for obj in objs 50 | print(*obj) 51 | print "\n" 52 | end 53 | end 54 | 55 | # Prints the given +objs+ calling Object#inspect on each. 56 | # 57 | # See #puts for more detail. 58 | def pp(*objs) 59 | puts(*objs.collect{|obj| obj.inspect}) 60 | end 61 | 62 | # Prints the given +objs+ calling Object#inspect on each and appending the 63 | # given +prefix+. 64 | # 65 | # See #puts for more detail. 66 | def ppx(prefix, *objs) 67 | puts(*objs.collect{|obj| prefix+obj.inspect}) 68 | end 69 | 70 | end 71 | 72 | # A standard output printer 73 | class StdioOutputMethod < OutputMethod 74 | # Prints the given +opts+ to standard output, see IO#print for more 75 | # information. 76 | def print(*opts) 77 | STDOUT.print(*opts) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/irb/source_finder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "ruby-lex" 4 | 5 | module IRB 6 | class SourceFinder 7 | class EvaluationError < StandardError; end 8 | 9 | class Source 10 | attr_reader :file, :line 11 | def initialize(file, line, ast_source = nil) 12 | @file = file 13 | @line = line 14 | @ast_source = ast_source 15 | end 16 | 17 | def file_exist? 18 | File.exist?(@file) 19 | end 20 | 21 | def binary_file? 22 | # If the line is zero, it means that the target's source is probably in a binary file. 23 | @line.zero? 24 | end 25 | 26 | def file_content 27 | @file_content ||= File.read(@file) 28 | end 29 | 30 | def colorized_content 31 | if !binary_file? && file_exist? 32 | end_line = find_end 33 | # To correctly colorize, we need to colorize full content and extract the relevant lines. 34 | colored = IRB::Color.colorize_code(file_content) 35 | colored.lines[@line - 1...end_line].join 36 | elsif @ast_source 37 | IRB::Color.colorize_code(@ast_source) 38 | end 39 | end 40 | 41 | private 42 | 43 | def find_end 44 | lex = RubyLex.new 45 | code = file_content 46 | lines = code.lines[(@line - 1)..-1] 47 | tokens = RubyLex.ripper_lex_without_warning(lines.join) 48 | prev_tokens = [] 49 | 50 | # chunk with line number 51 | tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| 52 | code = lines[0..lnum].join 53 | prev_tokens.concat chunk 54 | continue = lex.should_continue?(prev_tokens) 55 | syntax = lex.check_code_syntax(code, local_variables: []) 56 | if !continue && syntax == :valid 57 | return @line + lnum 58 | end 59 | end 60 | @line 61 | end 62 | end 63 | 64 | private_constant :Source 65 | 66 | def initialize(irb_context) 67 | @irb_context = irb_context 68 | end 69 | 70 | def find_source(signature, super_level = 0) 71 | case signature 72 | when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName 73 | eval_receiver_or_owner(signature) # trigger autoload 74 | *parts, name = signature.split('::', -1) 75 | base = 76 | if parts.empty? # ConstName 77 | find_const_owner(name) 78 | elsif parts == [''] # ::ConstName 79 | Object 80 | else # ConstPath::ConstName 81 | eval_receiver_or_owner(parts.join('::')) 82 | end 83 | file, line = base.const_source_location(name) 84 | when /\A(?[A-Z]\w*(::[A-Z]\w*)*)#(?[^ :.]+)\z/ # Class#method 85 | owner = eval_receiver_or_owner(Regexp.last_match[:owner]) 86 | method = Regexp.last_match[:method] 87 | return unless owner.respond_to?(:instance_method) 88 | method = method_target(owner, super_level, method, "owner") 89 | file, line = method&.source_location 90 | when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method 91 | receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self') 92 | method = Regexp.last_match[:method] 93 | return unless receiver.respond_to?(method, true) 94 | method = method_target(receiver, super_level, method, "receiver") 95 | file, line = method&.source_location 96 | end 97 | return unless file && line 98 | 99 | if File.exist?(file) 100 | Source.new(file, line) 101 | elsif method 102 | # Method defined with eval, probably in IRB session 103 | source = RubyVM::InstructionSequence.of(method)&.script_lines&.join rescue nil 104 | Source.new(file, line, source) 105 | end 106 | rescue EvaluationError 107 | nil 108 | end 109 | 110 | private 111 | 112 | def method_target(owner_receiver, super_level, method, type) 113 | case type 114 | when "owner" 115 | target_method = owner_receiver.instance_method(method) 116 | when "receiver" 117 | target_method = owner_receiver.method(method) 118 | end 119 | super_level.times do |s| 120 | target_method = target_method.super_method if target_method 121 | end 122 | target_method 123 | rescue NameError 124 | nil 125 | end 126 | 127 | def eval_receiver_or_owner(code) 128 | @irb_context.workspace.binding.eval(code) 129 | rescue Exception 130 | raise EvaluationError 131 | end 132 | 133 | def find_const_owner(name) 134 | module_nesting = @irb_context.workspace.binding.eval('::Module.nesting') 135 | module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/irb/statement.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IRB 4 | class Statement 5 | attr_reader :code 6 | 7 | def is_assignment? 8 | raise NotImplementedError 9 | end 10 | 11 | def suppresses_echo? 12 | raise NotImplementedError 13 | end 14 | 15 | def should_be_handled_by_debugger? 16 | raise NotImplementedError 17 | end 18 | 19 | class EmptyInput < Statement 20 | def is_assignment? 21 | false 22 | end 23 | 24 | def suppresses_echo? 25 | true 26 | end 27 | 28 | # Debugger takes empty input to repeat the last command 29 | def should_be_handled_by_debugger? 30 | true 31 | end 32 | 33 | def code 34 | "" 35 | end 36 | end 37 | 38 | class Expression < Statement 39 | def initialize(code, is_assignment) 40 | @code = code 41 | @is_assignment = is_assignment 42 | end 43 | 44 | def suppresses_echo? 45 | @code.match?(/;\s*\z/) 46 | end 47 | 48 | def should_be_handled_by_debugger? 49 | true 50 | end 51 | 52 | def is_assignment? 53 | @is_assignment 54 | end 55 | end 56 | 57 | class IncorrectAlias < Statement 58 | attr_reader :message 59 | 60 | def initialize(message) 61 | @code = "" 62 | @message = message 63 | end 64 | 65 | def should_be_handled_by_debugger? 66 | false 67 | end 68 | 69 | def is_assignment? 70 | false 71 | end 72 | 73 | def suppresses_echo? 74 | true 75 | end 76 | end 77 | 78 | class Command < Statement 79 | attr_reader :command_class, :arg 80 | 81 | def initialize(original_code, command_class, arg) 82 | @code = original_code 83 | @command_class = command_class 84 | @arg = arg 85 | end 86 | 87 | def is_assignment? 88 | false 89 | end 90 | 91 | def suppresses_echo? 92 | true 93 | end 94 | 95 | def should_be_handled_by_debugger? 96 | require_relative 'command/debug' 97 | IRB::Command::DebugCommand > @command_class 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/irb/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/version.rb - irb version definition file 4 | # by Keiju ISHITSUKA(keiju@ishitsuka.com) 5 | # 6 | 7 | module IRB # :nodoc: 8 | VERSION = "1.15.2" 9 | @RELEASE_VERSION = VERSION 10 | @LAST_UPDATE_DATE = "2025-04-03" 11 | end 12 | -------------------------------------------------------------------------------- /lib/irb/workspace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/workspace-binding.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | require_relative "helper_method" 8 | 9 | IRB::TOPLEVEL_BINDING = binding 10 | module IRB # :nodoc: 11 | class WorkSpace 12 | # Creates a new workspace. 13 | # 14 | # set self to main if specified, otherwise 15 | # inherit main from TOPLEVEL_BINDING. 16 | def initialize(*main) 17 | if Binding === main[0] 18 | @binding = main.shift 19 | elsif IRB.conf[:SINGLE_IRB] 20 | @binding = TOPLEVEL_BINDING 21 | else 22 | case IRB.conf[:CONTEXT_MODE] 23 | when 0 # binding in proc on TOPLEVEL_BINDING 24 | @binding = eval("proc{binding}.call", 25 | TOPLEVEL_BINDING, 26 | __FILE__, 27 | __LINE__) 28 | when 1 # binding in loaded file 29 | require "tempfile" 30 | f = Tempfile.open("irb-binding") 31 | f.print <IRB.conf[:__MAIN__] 88 | attr_reader :main 89 | 90 | def load_helper_methods_to_main 91 | # Do not load helper methods to frozen objects and BasicObject 92 | return unless Object === @main && !@main.frozen? 93 | 94 | ancestors = class<' : '', current_pos + 1, lines[current_pos]) 152 | end.join("") 153 | 154 | "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n" 155 | end 156 | end 157 | 158 | module HelpersContainer 159 | class << self 160 | def install_helper_methods 161 | HelperMethod.helper_methods.each do |name, helper_method_class| 162 | define_method name do |*args, **opts, &block| 163 | helper_method_class.instance.execute(*args, **opts, &block) 164 | end unless method_defined?(name) 165 | end 166 | end 167 | end 168 | 169 | install_helper_methods 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/irb/ws-for-case-2.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # irb/ws-for-case-2.rb - 4 | # by Keiju ISHITSUKA(keiju@ruby-lang.org) 5 | # 6 | 7 | while true 8 | IRB::BINDING_QUEUE.push _ = binding 9 | end 10 | -------------------------------------------------------------------------------- /lib/irb/xmp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # xmp.rb - irb version of gotoken xmp 4 | # by Keiju ISHITSUKA(Nippon Rational Inc.) 5 | # 6 | 7 | require_relative "../irb" 8 | require_relative "frame" 9 | 10 | # An example printer for irb. 11 | # 12 | # It's much like the standard library PrettyPrint, that shows the value of each 13 | # expression as it runs. 14 | # 15 | # In order to use this library, you must first require it: 16 | # 17 | # require 'irb/xmp' 18 | # 19 | # Now, you can take advantage of the Object#xmp convenience method. 20 | # 21 | # xmp < foo = "bar" 26 | # #==>"bar" 27 | # #=> baz = 42 28 | # #==>42 29 | # 30 | # You can also create an XMP object, with an optional binding to print 31 | # expressions in the given binding: 32 | # 33 | # ctx = binding 34 | # x = XMP.new ctx 35 | # x.puts 36 | # #=> today = "a good day" 37 | # #==>"a good day" 38 | # ctx.eval 'today # is what?' 39 | # #=> "a good day" 40 | class XMP 41 | 42 | # Creates a new XMP object. 43 | # 44 | # The top-level binding or, optional +bind+ parameter will be used when 45 | # creating the workspace. See WorkSpace.new for more information. 46 | # 47 | # This uses the +:XMP+ prompt mode. 48 | # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. 49 | def initialize(bind = nil) 50 | IRB.init_config(nil) 51 | 52 | IRB.conf[:PROMPT_MODE] = :XMP 53 | 54 | bind = IRB::Frame.top(1) unless bind 55 | ws = IRB::WorkSpace.new(bind) 56 | @io = StringInputMethod.new 57 | @irb = IRB::Irb.new(ws, @io) 58 | @irb.context.ignore_sigint = false 59 | 60 | IRB.conf[:MAIN_CONTEXT] = @irb.context 61 | end 62 | 63 | # Evaluates the given +exps+, for example: 64 | # 65 | # require 'irb/xmp' 66 | # x = XMP.new 67 | # 68 | # x.puts '{:a => 1, :b => 2, :c => 3}' 69 | # #=> {:a => 1, :b => 2, :c => 3} 70 | # # ==>{:a=>1, :b=>2, :c=>3} 71 | # x.puts 'foo = "bar"' 72 | # # => foo = "bar" 73 | # # ==>"bar" 74 | def puts(exps) 75 | @io.puts exps 76 | 77 | if @irb.context.ignore_sigint 78 | begin 79 | trap_proc_b = trap("SIGINT"){@irb.signal_handle} 80 | catch(:IRB_EXIT) do 81 | @irb.eval_input 82 | end 83 | ensure 84 | trap("SIGINT", trap_proc_b) 85 | end 86 | else 87 | catch(:IRB_EXIT) do 88 | @irb.eval_input 89 | end 90 | end 91 | end 92 | 93 | # A custom InputMethod class used by XMP for evaluating string io. 94 | class StringInputMethod < IRB::InputMethod 95 | # Creates a new StringInputMethod object 96 | def initialize 97 | super 98 | @exps = [] 99 | end 100 | 101 | # Whether there are any expressions left in this printer. 102 | def eof? 103 | @exps.empty? 104 | end 105 | 106 | # Reads the next expression from this printer. 107 | # 108 | # See IO#gets for more information. 109 | def gets 110 | while l = @exps.shift 111 | next if /^\s+$/ =~ l 112 | l.concat "\n" 113 | print @prompt, l 114 | break 115 | end 116 | l 117 | end 118 | 119 | # Concatenates all expressions in this printer, separated by newlines. 120 | # 121 | # An Encoding::CompatibilityError is raised of the given +exps+'s encoding 122 | # doesn't match the previous expression evaluated. 123 | def puts(exps) 124 | if @encoding and exps.encoding != @encoding 125 | enc = Encoding.compatible?(@exps.join("\n"), exps) 126 | if enc.nil? 127 | raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one" 128 | else 129 | @encoding = enc 130 | end 131 | else 132 | @encoding = exps.encoding 133 | end 134 | @exps.concat exps.split(/\n/) 135 | end 136 | 137 | # Returns the encoding of last expression printed by #puts. 138 | attr_reader :encoding 139 | end 140 | end 141 | 142 | # A convenience method that's only available when the you require the IRB::XMP standard library. 143 | # 144 | # Creates a new XMP object, using the given expressions as the +exps+ 145 | # parameter, and optional binding as +bind+ or uses the top-level binding. Then 146 | # evaluates the given expressions using the +:XMP+ prompt mode. 147 | # 148 | # For example: 149 | # 150 | # require 'irb/xmp' 151 | # ctx = binding 152 | # xmp 'foo = "bar"', ctx 153 | # #=> foo = "bar" 154 | # #==>"bar" 155 | # ctx.eval 'foo' 156 | # #=> "bar" 157 | # 158 | # See XMP.new for more information. 159 | def xmp(exps, bind = nil) 160 | bind = IRB::Frame.top(1) unless bind 161 | xmp = XMP.new(bind) 162 | xmp.puts exps 163 | xmp 164 | end 165 | -------------------------------------------------------------------------------- /man/irb.1: -------------------------------------------------------------------------------- 1 | .\"Ruby is copyrighted by Yukihiro Matsumoto . 2 | .Dd August 11, 2019 3 | .Dt IRB \&1 "Ruby Programmer's Reference Guide" 4 | .Os UNIX 5 | .Sh NAME 6 | .Nm irb 7 | .Nd Interactive Ruby Shell 8 | .Sh SYNOPSIS 9 | .Nm 10 | .Op Fl -version 11 | .Op Fl dfUw 12 | .Op Fl I Ar directory 13 | .Op Fl r Ar library 14 | .Op Fl E Ar external Ns Op : Ns Ar internal 15 | .Op Fl W Ns Op Ar level 16 | .Op Fl - Ns Oo no Oc Ns inspect 17 | .Op Fl - Ns Oo no Oc Ns multiline 18 | .Op Fl - Ns Oo no Oc Ns singleline 19 | .Op Fl - Ns Oo no Oc Ns echo 20 | .Op Fl - Ns Oo no Oc Ns colorize 21 | .Op Fl - Ns Oo no Oc Ns autocomplete 22 | .Op Fl - Ns Oo no Oc Ns verbose 23 | .Op Fl -prompt Ar mode 24 | .Op Fl -prompt-mode Ar mode 25 | .Op Fl -inf-ruby-mode 26 | .Op Fl -simple-prompt 27 | .Op Fl -noprompt 28 | .Op Fl -tracer 29 | .Op Fl -back-trace-limit Ar n 30 | .Op Fl - 31 | .Op program_file 32 | .Op argument ... 33 | .Pp 34 | .Sh DESCRIPTION 35 | .Nm 36 | is the REPL(read-eval-print loop) environment for Ruby programs. 37 | .Pp 38 | .Sh OPTIONS 39 | .Bl -tag -width "1234567890123" -compact 40 | .Pp 41 | .It Fl -version 42 | Prints the version of 43 | .Nm . 44 | .Pp 45 | .It Fl E Ar external Ns Op : Ns Ar internal 46 | .It Fl -encoding Ar external Ns Op : Ns Ar internal 47 | Same as `ruby -E' . 48 | Specifies the default value(s) for external encodings and internal encoding. Values should be separated with colon (:). 49 | .Pp 50 | You can omit the one for internal encodings, then the value 51 | .Pf ( Li "Encoding.default_internal" ) will be nil. 52 | .Pp 53 | .It Fl I Ar path 54 | Same as `ruby -I' . 55 | Specifies 56 | .Li $LOAD_PATH 57 | directory 58 | .Pp 59 | .It Fl U 60 | Same as `ruby -U' . 61 | Sets the default value for internal encodings 62 | .Pf ( Li "Encoding.default_internal" ) to UTF-8. 63 | .Pp 64 | .It Fl d 65 | Same as `ruby -d' . 66 | Sets 67 | .Li $DEBUG 68 | to true. 69 | .Pp 70 | .It Fl f 71 | Suppresses read of 72 | .Pa ~/.irbrc . 73 | .Pp 74 | .It Fl w 75 | Same as `ruby -w' . 76 | .Pp 77 | .Pp 78 | .It Fl W 79 | Same as `ruby -W' . 80 | .Pp 81 | .It Fl h 82 | .It Fl -help 83 | Prints a summary of the options. 84 | .Pp 85 | .It Fl r Ar library 86 | Same as `ruby -r'. 87 | Causes irb to load the library using require. 88 | .Pp 89 | .It Fl -inspect 90 | Uses `inspect' for output (default except for bc mode) 91 | .Pp 92 | .It Fl -noinspect 93 | Doesn't use inspect for output 94 | .Pp 95 | .It Fl -multiline 96 | Uses multiline editor module. 97 | .Pp 98 | .It Fl -nomultiline 99 | Doesn't use multiline editor module. 100 | .Pp 101 | .It Fl -singleline 102 | Uses singleline editor module. 103 | .Pp 104 | .It Fl -nosingleline 105 | Doesn't use singleline editor module. 106 | .Pp 107 | .Pp 108 | .It Fl -extra-doc-dir 109 | Add an extra doc dir for the doc dialog. 110 | .Pp 111 | .Pp 112 | .It Fl -echo 113 | Show result (default). 114 | .Pp 115 | .It Fl -noecho 116 | Don't show result. 117 | .Pp 118 | .Pp 119 | .It Fl -echo-on-assignment 120 | Show result on assignment. 121 | .Pp 122 | .It Fl -noecho-on-assignment 123 | Don't show result on assignment. 124 | .Pp 125 | .It Fl -truncate-echo-on-assignment 126 | Show truncated result on assignment (default). 127 | .Pp 128 | .Pp 129 | .It Fl -colorize 130 | Use colorization. 131 | .Pp 132 | .It Fl -nocolorize 133 | Don't use colorization. 134 | .Pp 135 | .Pp 136 | .It Fl -autocomplete 137 | Use autocompletion. 138 | .Pp 139 | .It Fl -noautocomplete 140 | Don't use autocompletion. 141 | .Pp 142 | .Pp 143 | .It Fl -regexp-completor 144 | Use regexp based completion. 145 | .Pp 146 | .It Fl -type-completor 147 | Use type based completion. 148 | .Pp 149 | .Pp 150 | .It Fl -verbose 151 | Show details. 152 | .Pp 153 | .It Fl -noverbose 154 | Don't show details. 155 | .Pp 156 | .It Fl -prompt Ar mode 157 | .It Fl -prompt-mode Ar mode 158 | Switch prompt mode. Pre-defined prompt modes are 159 | `default', `simple', `xmp' and `inf-ruby'. 160 | .Pp 161 | .It Fl -inf-ruby-mode 162 | Uses prompt appropriate for inf-ruby-mode on emacs. 163 | Suppresses --multiline and --singleline. 164 | .Pp 165 | .It Fl -simple-prompt 166 | Makes prompts simple. 167 | .Pp 168 | .It Fl -noprompt 169 | No prompt mode. 170 | .Pp 171 | .It Fl -tracer 172 | Displays trace for each execution of commands. 173 | .Pp 174 | .It Fl -back-trace-limit Ar n 175 | Displays backtrace top 176 | .Ar n 177 | and tail 178 | .Ar n Ns . 179 | The default value is 16. 180 | .El 181 | .Pp 182 | .Sh ENVIRONMENT 183 | .Bl -tag -compact -width "IRB_USE_AUTOCOMPLETE" 184 | .It Ev IRB_LANG 185 | The locale used for 186 | .Nm . 187 | .Pp 188 | .It Ev IRBRC 189 | The path to the personal initialization file. 190 | .Pp 191 | .It Ev XDG_CONFIG_HOME 192 | .Nm 193 | respects XDG_CONFIG_HOME. If it is set and 194 | .Ev IRBRC 195 | is unset, load 196 | .Pa $XDG_CONFIG_HOME/irb/irbrc 197 | as a personal initialization file. 198 | .Pp 199 | .It Ev RI_PAGER 200 | The command specified would be used as a pager. 201 | .Pp 202 | .It Ev PAGER 203 | The command specified would be used as a pager if 204 | .Ev RI_PAGER 205 | is unset. 206 | .Pp 207 | .It Ev VISUAL 208 | Its value would be used to open files by the edit command. 209 | .Pp 210 | .It Ev EDITOR 211 | Its value would be used to open files by the edit command if 212 | .Ev VISUAL 213 | is unset. 214 | .Pp 215 | .It Ev NO_COLOR 216 | Assigning a value to it disables colorization. 217 | .Pp 218 | .It Ev IRB_USE_AUTOCOMPLETE 219 | Assigning 220 | .Sy false 221 | to it disables autocompletion. 222 | .Pp 223 | .It Ev IRB_COMPLETOR 224 | Autocompletion behavior. Allowed values are 225 | .Sy regexp 226 | or 227 | .Sy type 228 | . 229 | .Pp 230 | .It Ev IRB_COPY_COMMAND 231 | Overrides the default program used to interface with the system clipboard. 232 | .El 233 | .Pp 234 | Also 235 | .Nm 236 | depends on same variables as 237 | .Xr ruby 1 . 238 | .Pp 239 | .Sh FILES 240 | .Bl -tag -compact 241 | .It Pa ~/.irbrc 242 | Personal irb initialization. If 243 | .Ev IRBRC 244 | is set, read 245 | .Pa $IRBRC 246 | instead. If 247 | .Ev IRBRC 248 | is not set and 249 | .Ev XDG_CONFIG_HOME 250 | is set, 251 | .Pa $XDG_CONFIG_HOME/irb/irbrc 252 | is loaded. 253 | .Pp 254 | .El 255 | .Pp 256 | .Sh EXAMPLES 257 | .Dl % irb 258 | .Dl irb(main):001:0> Ic 1 + 1 259 | .Dl 2 260 | .Dl irb(main):002:0> Ic def t(x) 261 | .Dl irb(main):003:1> Ic x + 1 262 | .Dl irb(main):004:1> Ic end 263 | .Dl => :t 264 | .Dl irb(main):005:0> Ic t(3) 265 | .Dl => 4 266 | .Dl irb(main):006:0> Ic if t(3) == 4 267 | .Dl irb(main):007:1> Ic p :ok 268 | .Dl irb(main):008:1> Ic end 269 | .Dl :ok 270 | .Dl => :ok 271 | .Dl irb(main):009:0> Ic quit 272 | .Dl % 273 | .Pp 274 | .Sh SEE ALSO 275 | .Xr ruby 1 . 276 | .Pp 277 | .Sh REPORTING BUGS 278 | .Bl -bullet 279 | .It 280 | Security vulnerabilities should be reported via an email to 281 | .Mt security@ruby-lang.org . 282 | Reported problems will be published after being fixed. 283 | .Pp 284 | .It 285 | Other bugs and feature requests can be reported via the 286 | Ruby Issue Tracking System 287 | .Pq Lk https://bugs.ruby-lang.org/ . 288 | Do not report security vulnerabilities 289 | via this system because it publishes the vulnerabilities immediately. 290 | .El 291 | .Sh AUTHORS 292 | Written by Keiju ISHITSUKA. 293 | -------------------------------------------------------------------------------- /test/irb/command/test_cd.rb: -------------------------------------------------------------------------------- 1 | require "tempfile" 2 | require_relative "../helper" 3 | 4 | module TestIRB 5 | class CDTest < IntegrationTestCase 6 | def setup 7 | super 8 | 9 | write_ruby <<~'RUBY' 10 | class Foo 11 | class Bar 12 | def bar 13 | "this is bar" 14 | end 15 | end 16 | 17 | def foo 18 | "this is foo" 19 | end 20 | end 21 | 22 | class BO < BasicObject 23 | def baz 24 | "this is baz" 25 | end 26 | end 27 | 28 | binding.irb 29 | RUBY 30 | end 31 | 32 | def test_cd 33 | out = run_ruby_file do 34 | type "cd Foo" 35 | type "ls" 36 | type "cd Bar" 37 | type "ls" 38 | type "cd .." 39 | type "exit" 40 | end 41 | 42 | assert_match(/irb\(Foo\):002>/, out) 43 | assert_match(/Foo#methods: foo/, out) 44 | assert_match(/irb\(Foo::Bar\):004>/, out) 45 | assert_match(/Bar#methods: bar/, out) 46 | assert_match(/irb\(Foo\):006>/, out) 47 | end 48 | 49 | def test_cd_basic_object_or_frozen 50 | out = run_ruby_file do 51 | type "cd BO.new" 52 | type "cd 1" 53 | type "cd Object.new.freeze" 54 | type "exit" 55 | end 56 | 57 | assert_match(/irb\(#/, out) 58 | assert_match(/irb\(1\):003>/, out) 59 | assert_match(/irb\(#/, out) 60 | end 61 | 62 | def test_cd_moves_top_level_with_no_args 63 | out = run_ruby_file do 64 | type "cd Foo" 65 | type "cd Bar" 66 | type "cd" 67 | type "exit" 68 | end 69 | 70 | assert_match(/irb\(Foo::Bar\):003>/, out) 71 | assert_match(/irb\(main\):004>/, out) 72 | end 73 | 74 | def test_cd_with_error 75 | out = run_ruby_file do 76 | type "cd Baz" 77 | type "exit" 78 | end 79 | 80 | assert_match(/Error: uninitialized constant Baz/, out) 81 | assert_match(/irb\(main\):002>/, out) # the context should not change 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/irb/command/test_command_aliasing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "tempfile" 4 | require_relative "../helper" 5 | 6 | module TestIRB 7 | class CommandAliasingTest < IntegrationTestCase 8 | def setup 9 | super 10 | write_rc <<~RUBY 11 | IRB.conf[:COMMAND_ALIASES] = { 12 | :c => :conf, # alias to helper method 13 | :f => :foo 14 | } 15 | RUBY 16 | 17 | write_ruby <<~'RUBY' 18 | binding.irb 19 | RUBY 20 | end 21 | 22 | def test_aliasing_to_helper_method_triggers_warning 23 | out = run_ruby_file do 24 | type "c" 25 | type "exit" 26 | end 27 | assert_include(out, "Using command alias `c` for helper method `conf` is not supported.") 28 | assert_not_include(out, "Maybe IRB bug!") 29 | end 30 | 31 | def test_alias_to_non_existent_command_triggers_warning 32 | message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist." 33 | out = run_ruby_file do 34 | type "f" 35 | type "exit" 36 | end 37 | assert_include(out, message) 38 | assert_not_include(out, "Maybe IRB bug!") 39 | 40 | # Local variables take precedence over command aliases 41 | out = run_ruby_file do 42 | type "f = 123" 43 | type "f" 44 | type "exit" 45 | end 46 | assert_not_include(out, message) 47 | assert_not_include(out, "Maybe IRB bug!") 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/irb/command/test_copy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'irb' 4 | 5 | require_relative "../helper" 6 | 7 | module TestIRB 8 | class CopyTest < IntegrationTestCase 9 | def setup 10 | super 11 | @envs['IRB_COPY_COMMAND'] = "#{EnvUtil.rubybin} -e \"puts 'foo' + STDIN.read\"" 12 | end 13 | 14 | def test_copy_with_pbcopy 15 | write_ruby <<~'ruby' 16 | class Answer 17 | def initialize(answer) 18 | @answer = answer 19 | end 20 | end 21 | 22 | binding.irb 23 | ruby 24 | 25 | output = run_ruby_file do 26 | type "copy Answer.new(42)" 27 | type "exit" 28 | end 29 | 30 | assert_match(/foo# "3"') 187 | assert_include(output, '=> "4"') 188 | assert_include(output, '=> "5"') 189 | assert_include(output, '=> "6"') 190 | assert_include(output, '=> "7"') 191 | assert_include(output, '=> "8"') 192 | end 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /test/irb/command/test_disable_irb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'irb' 3 | 4 | require_relative "../helper" 5 | 6 | module TestIRB 7 | class DisableIRBTest < IntegrationTestCase 8 | def test_disable_irb_disable_further_irb_breakpoints 9 | write_ruby <<~'ruby' 10 | puts "First line" 11 | puts "Second line" 12 | binding.irb 13 | puts "Third line" 14 | binding.irb 15 | puts "Fourth line" 16 | ruby 17 | 18 | output = run_ruby_file do 19 | type "disable_irb" 20 | end 21 | 22 | assert_match(/First line\r\n/, output) 23 | assert_match(/Second line\r\n/, output) 24 | assert_match(/Third line\r\n/, output) 25 | assert_match(/Fourth line\r\n/, output) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/irb/command/test_force_exit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'irb' 3 | 4 | require_relative "../helper" 5 | 6 | module TestIRB 7 | class ForceExitTest < IntegrationTestCase 8 | def test_forced_exit_finishes_process_immediately 9 | write_ruby <<~'ruby' 10 | puts "First line" 11 | puts "Second line" 12 | binding.irb 13 | puts "Third line" 14 | binding.irb 15 | puts "Fourth line" 16 | ruby 17 | 18 | output = run_ruby_file do 19 | type "123" 20 | type "456" 21 | type "exit!" 22 | end 23 | 24 | assert_match(/First line\r\n/, output) 25 | assert_match(/Second line\r\n/, output) 26 | assert_match(/irb\(main\):001> 123/, output) 27 | assert_match(/irb\(main\):002> 456/, output) 28 | refute_match(/Third line\r\n/, output) 29 | refute_match(/Fourth line\r\n/, output) 30 | end 31 | 32 | def test_forced_exit_in_nested_sessions 33 | write_ruby <<~'ruby' 34 | def foo 35 | binding.irb 36 | end 37 | 38 | binding.irb 39 | binding.irb 40 | ruby 41 | 42 | output = run_ruby_file do 43 | type "123" 44 | type "foo" 45 | type "exit!" 46 | end 47 | 48 | assert_match(/irb\(main\):001> 123/, output) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/irb/command/test_help.rb: -------------------------------------------------------------------------------- 1 | require "tempfile" 2 | require_relative "../helper" 3 | 4 | module TestIRB 5 | class HelpTest < IntegrationTestCase 6 | def setup 7 | super 8 | 9 | write_rc <<~'RUBY' 10 | IRB.conf[:USE_PAGER] = false 11 | RUBY 12 | 13 | write_ruby <<~'RUBY' 14 | binding.irb 15 | RUBY 16 | end 17 | 18 | def test_help 19 | out = run_ruby_file do 20 | type "help" 21 | type "exit" 22 | end 23 | 24 | assert_match(/List all available commands/, out) 25 | assert_match(/Start the debugger of debug\.gem/, out) 26 | end 27 | 28 | def test_command_help 29 | out = run_ruby_file do 30 | type "help ls" 31 | type "exit" 32 | end 33 | 34 | assert_match(/Usage: ls \[obj\]/, out) 35 | end 36 | 37 | def test_command_help_not_found 38 | out = run_ruby_file do 39 | type "help foo" 40 | type "exit" 41 | end 42 | 43 | assert_match(/Can't find command `foo`\. Please check the command name and try again\./, out) 44 | end 45 | 46 | def test_show_cmds 47 | out = run_ruby_file do 48 | type "help" 49 | type "exit" 50 | end 51 | 52 | assert_match(/List all available commands/, out) 53 | assert_match(/Start the debugger of debug\.gem/, out) 54 | end 55 | 56 | def test_help_lists_user_aliases 57 | out = run_ruby_file do 58 | type "help" 59 | type "exit" 60 | end 61 | 62 | assert_match(/\$\s+Alias for `show_source`/, out) 63 | assert_match(/@\s+Alias for `whereami`/, out) 64 | end 65 | 66 | def test_help_lists_helper_methods 67 | out = run_ruby_file do 68 | type "help" 69 | type "exit" 70 | end 71 | 72 | assert_match(/Helper methods\s+conf\s+Returns the current IRB context/, out) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/irb/command/test_multi_irb_commands.rb: -------------------------------------------------------------------------------- 1 | require "tempfile" 2 | require_relative "../helper" 3 | 4 | module TestIRB 5 | class MultiIRBTest < IntegrationTestCase 6 | def setup 7 | super 8 | 9 | write_ruby <<~'RUBY' 10 | binding.irb 11 | RUBY 12 | end 13 | 14 | def test_jobs_command_with_print_deprecated_warning 15 | out = run_ruby_file do 16 | type "jobs" 17 | type "exit" 18 | end 19 | 20 | assert_match(/Multi-irb commands are deprecated and will be removed in IRB 2\.0\.0\. Please use workspace commands instead\./, out) 21 | assert_match(%r|If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653|, out) 22 | assert_match(/#0->irb on main \(#: running\)/, out) 23 | end 24 | 25 | def test_irb_jobs_and_kill_commands 26 | out = run_ruby_file do 27 | type "irb" 28 | type "jobs" 29 | type "kill 1" 30 | type "exit" 31 | end 32 | 33 | assert_match(/#0->irb on main \(#: stop\)/, out) 34 | assert_match(/#1->irb#1 on main \(#: running\)/, out) 35 | end 36 | 37 | def test_irb_fg_jobs_and_kill_commands 38 | out = run_ruby_file do 39 | type "irb" 40 | type "fg 0" 41 | type "jobs" 42 | type "kill 1" 43 | type "exit" 44 | end 45 | 46 | assert_match(/#0->irb on main \(#: running\)/, out) 47 | assert_match(/#1->irb#1 on main \(#: stop\)/, out) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/irb/test_color_printer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'irb/color_printer' 3 | require 'stringio' 4 | 5 | require_relative "helper" 6 | 7 | module TestIRB 8 | class ColorPrinterTest < TestCase 9 | CLEAR = "\e[0m" 10 | BOLD = "\e[1m" 11 | RED = "\e[31m" 12 | GREEN = "\e[32m" 13 | BLUE = "\e[34m" 14 | CYAN = "\e[36m" 15 | 16 | def setup 17 | super 18 | if IRB.respond_to?(:conf) 19 | @colorize, IRB.conf[:USE_COLORIZE] = IRB.conf[:USE_COLORIZE], true 20 | end 21 | @get_screen_size = Reline.method(:get_screen_size) 22 | Reline.instance_eval { undef :get_screen_size } 23 | def Reline.get_screen_size 24 | [36, 80] 25 | end 26 | end 27 | 28 | def teardown 29 | Reline.instance_eval { undef :get_screen_size } 30 | Reline.define_singleton_method(:get_screen_size, @get_screen_size) 31 | if instance_variable_defined?(:@colorize) 32 | IRB.conf[:USE_COLORIZE] = @colorize 33 | end 34 | super 35 | end 36 | 37 | IRBTestColorPrinter = Struct.new(:a) 38 | 39 | def test_color_printer 40 | { 41 | 1 => "#{BLUE}#{BOLD}1#{CLEAR}\n", 42 | "a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{CLEAR}\n], 43 | IRBTestColorPrinter.new('test') => "#{GREEN}##{CLEAR}\n", 44 | Ripper::Lexer.new('1').scan => "[#{GREEN}##{CLEAR}]\n", 45 | Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n", 46 | }.each do |object, result| 47 | actual = with_term { IRB::ColorPrinter.pp(object, '') } 48 | assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") 49 | end 50 | end 51 | 52 | def test_colorization_disabled 53 | { 54 | 1 => "1\n", 55 | "a\nb" => %["a\\nb"\n], 56 | IRBTestColorPrinter.new('test') => "#\n", 57 | Ripper::Lexer.new('1').scan => "[#]\n", 58 | Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[__FILE__, __LINE__, __ENCODING__]\n", 59 | }.each do |object, result| 60 | actual = with_term { IRB::ColorPrinter.pp(object, '', colorize: false) } 61 | assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") 62 | end 63 | end 64 | 65 | private 66 | 67 | def with_term 68 | stdout = $stdout 69 | io = StringIO.new 70 | def io.tty?; true; end 71 | $stdout = io 72 | 73 | env = ENV.to_h.dup 74 | ENV['TERM'] = 'xterm-256color' 75 | 76 | yield 77 | ensure 78 | $stdout = stdout 79 | ENV.replace(env) if env 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/irb/test_eval_history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "irb" 3 | 4 | require_relative "helper" 5 | 6 | module TestIRB 7 | class EvalHistoryTest < TestCase 8 | def setup 9 | save_encodings 10 | IRB.instance_variable_get(:@CONF).clear 11 | end 12 | 13 | def teardown 14 | restore_encodings 15 | end 16 | 17 | def test_eval_history_is_disabled_by_default 18 | out, err = execute_lines( 19 | "a = 1", 20 | "__" 21 | ) 22 | 23 | assert_empty(err) 24 | assert_match(/undefined local variable or method (`|')__'/, out) 25 | end 26 | 27 | def test_eval_history_can_be_retrieved_with_double_underscore 28 | out, err = execute_lines( 29 | "a = 1", 30 | "__", 31 | conf: { EVAL_HISTORY: 5 } 32 | ) 33 | 34 | assert_empty(err) 35 | assert_match("=> 1\n" + "=> 1 1\n", out) 36 | end 37 | 38 | def test_eval_history_respects_given_limit 39 | out, err = execute_lines( 40 | "'foo'\n", 41 | "'bar'\n", 42 | "'baz'\n", 43 | "'xyz'\n", 44 | "__", 45 | conf: { EVAL_HISTORY: 4 } 46 | ) 47 | 48 | assert_empty(err) 49 | # Because eval_history injects `__` into the history AND decide to ignore it, we only get - 1 results 50 | assert_match("2 \"bar\"\n" + "3 \"baz\"\n" + "4 \"xyz\"\n", out) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/irb/test_evaluation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "tempfile" 4 | 5 | require_relative "helper" 6 | 7 | module TestIRB 8 | class EchoingTest < IntegrationTestCase 9 | def test_irb_echos_by_default 10 | write_ruby <<~'RUBY' 11 | binding.irb 12 | RUBY 13 | 14 | output = run_ruby_file do 15 | type "123123" 16 | type "exit" 17 | end 18 | 19 | assert_include(output, "=> 123123") 20 | end 21 | 22 | def test_irb_doesnt_echo_line_with_semicolon 23 | write_ruby <<~'RUBY' 24 | binding.irb 25 | RUBY 26 | 27 | output = run_ruby_file do 28 | type "123123;" 29 | type "123123 ;" 30 | type "123123; " 31 | type <<~RUBY 32 | if true 33 | 123123 34 | end; 35 | RUBY 36 | type "'evaluation ends'" 37 | type "exit" 38 | end 39 | 40 | assert_include(output, "=> \"evaluation ends\"") 41 | assert_not_include(output, "=> 123123") 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/irb/test_helper_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "irb" 3 | 4 | require_relative "helper" 5 | 6 | module TestIRB 7 | class HelperMethodTestCase < TestCase 8 | def setup 9 | $VERBOSE = nil 10 | @verbosity = $VERBOSE 11 | save_encodings 12 | IRB.instance_variable_get(:@CONF).clear 13 | end 14 | 15 | def teardown 16 | $VERBOSE = @verbosity 17 | restore_encodings 18 | end 19 | end 20 | 21 | module TestHelperMethod 22 | class ConfTest < HelperMethodTestCase 23 | def test_conf_returns_the_context_object 24 | out, err = execute_lines("conf.ap_name") 25 | 26 | assert_empty err 27 | assert_include out, "=> \"irb\"" 28 | end 29 | end 30 | end 31 | 32 | class HelperMethodIntegrationTest < IntegrationTestCase 33 | def test_arguments_propogation 34 | write_ruby <<~RUBY 35 | require "irb/helper_method" 36 | 37 | class MyHelper < IRB::HelperMethod::Base 38 | description "This is a test helper" 39 | 40 | def execute( 41 | required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:, 42 | optional_keyword_arg: nil, **double_splat_arg, &block_arg 43 | ) 44 | puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s 45 | end 46 | end 47 | 48 | IRB::HelperMethod.register(:my_helper, MyHelper) 49 | 50 | binding.irb 51 | RUBY 52 | 53 | output = run_ruby_file do 54 | type <<~INPUT 55 | my_helper( 56 | "required", "optional", "splat", required_keyword_arg: "required", 57 | optional_keyword_arg: "optional", a: 1, b: 2 58 | ) { "block" } 59 | INPUT 60 | type "exit" 61 | end 62 | 63 | optional = {a: 1, b: 2} 64 | assert_include(output, %[["required", "optional", ["splat"], "required", "optional", #{optional.inspect}, "block"]]) 65 | end 66 | 67 | def test_helper_method_injection_can_happen_after_irb_require 68 | write_ruby <<~RUBY 69 | require "irb" 70 | 71 | class MyHelper < IRB::HelperMethod::Base 72 | description "This is a test helper" 73 | 74 | def execute 75 | puts "Hello from MyHelper" 76 | end 77 | end 78 | 79 | IRB::HelperMethod.register(:my_helper, MyHelper) 80 | 81 | binding.irb 82 | RUBY 83 | 84 | output = run_ruby_file do 85 | type "my_helper" 86 | type "exit" 87 | end 88 | 89 | assert_include(output, 'Hello from MyHelper') 90 | end 91 | 92 | def test_helper_method_instances_are_memoized 93 | write_ruby <<~RUBY 94 | require "irb/helper_method" 95 | 96 | class MyHelper < IRB::HelperMethod::Base 97 | description "This is a test helper" 98 | 99 | def execute(val) 100 | @val ||= val 101 | end 102 | end 103 | 104 | IRB::HelperMethod.register(:my_helper, MyHelper) 105 | 106 | binding.irb 107 | RUBY 108 | 109 | output = run_ruby_file do 110 | type "my_helper(100)" 111 | type "my_helper(200)" 112 | type "exit" 113 | end 114 | 115 | assert_include(output, '=> 100') 116 | assert_not_include(output, '=> 200') 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /test/irb/test_locale.rb: -------------------------------------------------------------------------------- 1 | require "irb" 2 | require "stringio" 3 | 4 | require_relative "helper" 5 | 6 | module TestIRB 7 | class LocaleTestCase < TestCase 8 | def test_initialize_with_en 9 | locale = IRB::Locale.new("en_US.UTF-8") 10 | 11 | assert_equal("en", locale.lang) 12 | assert_equal("US", locale.territory) 13 | assert_equal("UTF-8", locale.encoding.name) 14 | assert_equal(nil, locale.modifier) 15 | end 16 | 17 | def test_initialize_with_ja 18 | locale = IRB::Locale.new("ja_JP.UTF-8") 19 | 20 | assert_equal("ja", locale.lang) 21 | assert_equal("JP", locale.territory) 22 | assert_equal("UTF-8", locale.encoding.name) 23 | assert_equal(nil, locale.modifier) 24 | end 25 | 26 | def test_initialize_with_legacy_ja_encoding_ujis 27 | original_stderr = $stderr 28 | $stderr = StringIO.new 29 | 30 | locale = IRB::Locale.new("ja_JP.ujis") 31 | 32 | assert_equal("ja", locale.lang) 33 | assert_equal("JP", locale.territory) 34 | assert_equal(Encoding::EUC_JP, locale.encoding) 35 | assert_equal(nil, locale.modifier) 36 | 37 | assert_include $stderr.string, "ja_JP.ujis is obsolete. use ja_JP.EUC-JP" 38 | ensure 39 | $stderr = original_stderr 40 | end 41 | 42 | def test_initialize_with_legacy_ja_encoding_euc 43 | original_stderr = $stderr 44 | $stderr = StringIO.new 45 | 46 | locale = IRB::Locale.new("ja_JP.euc") 47 | 48 | assert_equal("ja", locale.lang) 49 | assert_equal("JP", locale.territory) 50 | assert_equal(Encoding::EUC_JP, locale.encoding) 51 | assert_equal(nil, locale.modifier) 52 | 53 | assert_include $stderr.string, "ja_JP.euc is obsolete. use ja_JP.EUC-JP" 54 | ensure 55 | $stderr = original_stderr 56 | end 57 | 58 | %w(IRB_LANG LC_MESSAGES LC_ALL LANG).each do |env_var| 59 | define_method "test_initialize_with_#{env_var.downcase}" do 60 | original_values = { 61 | "IRB_LANG" => ENV["IRB_LANG"], 62 | "LC_MESSAGES" => ENV["LC_MESSAGES"], 63 | "LC_ALL" => ENV["LC_ALL"], 64 | "LANG" => ENV["LANG"], 65 | } 66 | 67 | ENV["IRB_LANG"] = ENV["LC_MESSAGES"] = ENV["LC_ALL"] = ENV["LANG"] = nil 68 | ENV[env_var] = "zh_TW.UTF-8" 69 | 70 | locale = IRB::Locale.new 71 | 72 | assert_equal("zh", locale.lang) 73 | assert_equal("TW", locale.territory) 74 | assert_equal("UTF-8", locale.encoding.name) 75 | assert_equal(nil, locale.modifier) 76 | ensure 77 | original_values.each do |key, value| 78 | ENV[key] = value 79 | end 80 | end 81 | end 82 | 83 | def test_load 84 | # reset Locale's internal cache 85 | IRB::Locale.class_variable_set(:@@loaded, []) 86 | # Because error.rb files define the same class, loading them causes method redefinition warnings. 87 | original_verbose = $VERBOSE 88 | $VERBOSE = nil 89 | 90 | jp_local = IRB::Locale.new("ja_JP.UTF-8") 91 | jp_local.load("irb/error.rb") 92 | msg = IRB::CantReturnToNormalMode.new.message 93 | assert_equal("Normalモードに戻れません.", msg) 94 | 95 | # reset Locale's internal cache 96 | IRB::Locale.class_variable_set(:@@loaded, []) 97 | 98 | en_local = IRB::Locale.new("en_US.UTF-8") 99 | en_local.load("irb/error.rb") 100 | msg = IRB::CantReturnToNormalMode.new.message 101 | assert_equal("Can't return to normal mode.", msg) 102 | ensure 103 | # before turning warnings back on, load the error.rb file again to avoid warnings in other tests 104 | IRB::Locale.new.load("irb/error.rb") 105 | $VERBOSE = original_verbose 106 | end 107 | 108 | def test_find 109 | jp_local = IRB::Locale.new("ja_JP.UTF-8") 110 | path = jp_local.find("irb/error.rb") 111 | assert_include(path, "/lib/irb/lc/ja/error.rb") 112 | 113 | en_local = IRB::Locale.new("en_US.UTF-8") 114 | path = en_local.find("irb/error.rb") 115 | assert_include(path, "/lib/irb/lc/error.rb") 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /test/irb/test_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require_relative "helper" 3 | 4 | module TestIRB 5 | class OptionTest < TestCase 6 | def test_end_of_option 7 | bug4117 = '[ruby-core:33574]' 8 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 9 | libdir = File.expand_path("../../lib", __dir__) 10 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 11 | status = assert_in_out_err(bundle_exec + %W[-W0 -I#{libdir} -I#{reline_libdir} -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) 12 | assert(status.success?, bug4117) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/irb/test_pager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'irb/pager' 3 | 4 | require_relative 'helper' 5 | 6 | module TestIRB 7 | class PagerTest < TestCase 8 | def test_take_first_page 9 | assert_equal ['a' * 40, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 41; raise 'should not reach here' } 10 | assert_equal ["a\nb\na\nb\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\nb\n" } } 11 | assert_equal ["a\n\n\na\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\n\n\n" } } 12 | assert_equal ["11\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write 1; io.puts 1 } } 13 | assert_equal ["\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write nil; io.puts nil } } 14 | assert_equal ['a' * 39, false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 } 15 | assert_equal ['a' * 39 + 'b', false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'b' } 16 | assert_equal ['a' * 39 + 'b', true], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'bc' } 17 | assert_equal ["a\nb\nc\nd\n", false], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\n" } 18 | assert_equal ["a\nb\nc\nd\n", true], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\ne" } 19 | assert_equal ['a' * 15 + "\n" + 'b' * 20, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 15; io.puts 'b' * 30 } 20 | assert_equal ["\e[31mA\e[0m" * 10 + 'x' * 30, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts "\e[31mA\e[0m" * 10 + 'x' * 31 } 21 | text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mA\e[0m" } } 22 | assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] 23 | text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mAAA\e[0m" } } 24 | assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] 25 | end 26 | end 27 | 28 | class PageOverflowIOTest < TestCase 29 | def test_overflow 30 | actual_events = [] 31 | overflow_callback = ->(lines) do 32 | actual_events << [:callback_called, lines] 33 | end 34 | out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback) 35 | out.puts 'a' * 15 36 | out.write 'b' * 15 37 | 38 | actual_events << :before_write 39 | out.write 'c' * 1000 40 | actual_events << :after_write 41 | 42 | out.puts 'd' * 1000 43 | out.write 'e' * 1000 44 | 45 | expected_events = [ 46 | :before_write, 47 | [:callback_called, ['a' * 10, 'a' * 5 + "\n", 'b' * 10, 'b' * 5 + 'c' * 5]], 48 | :after_write, 49 | ] 50 | assert_equal expected_events, actual_events 51 | 52 | expected_whole_content = 'a' * 15 + "\n" + 'b' * 15 + 'c' * 1000 + 'd' * 1000 + "\n" + 'e' * 1000 53 | assert_equal expected_whole_content, out.string 54 | end 55 | 56 | def test_callback_delay 57 | actual_events = [] 58 | overflow_callback = ->(lines) do 59 | actual_events << [:callback_called, lines] 60 | end 61 | out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback, delay: 0.2) 62 | out.write 'a' * 1000 63 | assert_equal ['a' * 10] * 4, out.first_page_lines 64 | out.write 'b' 65 | actual_events << :before_delay 66 | sleep 0.2 67 | out.write 'c' 68 | actual_events << :after_delay 69 | out.write 'd' 70 | assert_equal 'a' * 1000 + 'bcd', out.string 71 | assert_equal [:before_delay, [:callback_called, ['a' * 10] * 4], :after_delay], actual_events 72 | end 73 | 74 | def test_zero_width 75 | out = IRB::Pager::PageOverflowIO.new(0, 0, ->*{}) 76 | 100.times { out.write 'a' } 77 | assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] 78 | out = IRB::Pager::PageOverflowIO.new(10, 0, ->*{}) 79 | 100.times { out.write 'a' } 80 | assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] 81 | out = IRB::Pager::PageOverflowIO.new(0, 10, ->*{}) 82 | 100.times { out.write 'a' } 83 | assert_equal [true, [], 'a' * 100], [out.multipage?, out.first_page_lines, out.string] 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/irb/test_raise_exception.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require "tmpdir" 3 | 4 | require_relative "helper" 5 | 6 | module TestIRB 7 | class RaiseExceptionTest < TestCase 8 | def test_raise_exception_with_nil_backtrace 9 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 10 | libdir = File.expand_path("../../lib", __dir__) 11 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 12 | assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#/, []) 13 | raise Exception.new("foo").tap {|e| def e.backtrace; nil; end } 14 | IRB 15 | end 16 | 17 | def test_raise_exception_with_message_exception 18 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 19 | libdir = File.expand_path("../../lib", __dir__) 20 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 21 | expected = /#\nbacktraces are hidden because bar was raised when processing them/ 22 | assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) 23 | e = Exception.new("foo") 24 | def e.message; raise 'bar'; end 25 | raise e 26 | IRB 27 | end 28 | 29 | def test_raise_exception_with_message_inspect_exception 30 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 31 | libdir = File.expand_path("../../lib", __dir__) 32 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 33 | expected = /Uninspectable exception occurred/ 34 | assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) 35 | e = Exception.new("foo") 36 | def e.message; raise; end 37 | def e.inspect; raise; end 38 | raise e 39 | IRB 40 | end 41 | 42 | def test_raise_exception_with_invalid_byte_sequence 43 | pend if RUBY_ENGINE == 'truffleruby' || /mswin|mingw/ =~ RUBY_PLATFORM 44 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 45 | libdir = File.expand_path("../../lib", __dir__) 46 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 47 | assert_in_out_err(bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -W0 -e IRB.start(__FILE__) -- -f --], <<~IRB, /A\\xF3B \(StandardError\)/, []) 48 | raise StandardError, "A\\xf3B" 49 | IRB 50 | end 51 | 52 | def test_raise_exception_with_different_encoding_containing_invalid_byte_sequence 53 | backup_home = ENV["HOME"] 54 | Dir.mktmpdir("test_irb_raise_no_backtrace_exception_#{$$}") do |tmpdir| 55 | ENV["HOME"] = tmpdir 56 | 57 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 58 | libdir = File.expand_path("../../lib", __dir__) 59 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 60 | File.open("#{tmpdir}/euc.rb", 'w') do |f| 61 | f.write(<<~EOF) 62 | # encoding: euc-jp 63 | 64 | def raise_euc_with_invalid_byte_sequence 65 | raise "\xA4\xA2\\xFF" 66 | end 67 | EOF 68 | end 69 | env = {} 70 | %w(LC_MESSAGES LC_ALL LC_CTYPE LANG).each {|n| env[n] = "ja_JP.UTF-8" } 71 | # TruffleRuby warns when the locale does not exist 72 | env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby' 73 | args = [env] + bundle_exec + %W[-I#{libdir} -I#{reline_libdir} -rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --] 74 | error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/ 75 | assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8") 76 | require_relative 'euc' 77 | raise_euc_with_invalid_byte_sequence 78 | IRB 79 | end 80 | ensure 81 | ENV["HOME"] = backup_home 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/irb/test_tracer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'tempfile' 3 | require 'irb' 4 | 5 | require_relative "helper" 6 | 7 | module TestIRB 8 | class ContextWithTracerIntegrationTest < IntegrationTestCase 9 | def setup 10 | super 11 | 12 | omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby" 13 | 14 | @envs.merge!("NO_COLOR" => "true") 15 | end 16 | 17 | def example_ruby_file 18 | <<~'RUBY' 19 | class Foo 20 | def self.foo 21 | 100 22 | end 23 | end 24 | 25 | def bar(obj) 26 | obj.foo 27 | end 28 | 29 | binding.irb 30 | RUBY 31 | end 32 | 33 | def test_use_tracer_enabled_when_gem_is_unavailable 34 | write_rc <<~RUBY 35 | # Simulate the absence of the tracer gem 36 | ::Kernel.send(:alias_method, :irb_original_require, :require) 37 | 38 | ::Kernel.define_method(:require) do |name| 39 | raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer") 40 | ::Kernel.send(:irb_original_require, name) 41 | end 42 | 43 | IRB.conf[:USE_TRACER] = true 44 | RUBY 45 | 46 | write_ruby example_ruby_file 47 | 48 | output = run_ruby_file do 49 | type "bar(Foo)" 50 | type "exit" 51 | end 52 | 53 | assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") 54 | end 55 | 56 | def test_use_tracer_enabled_when_gem_is_available 57 | write_rc <<~RUBY 58 | IRB.conf[:USE_TRACER] = true 59 | RUBY 60 | 61 | write_ruby example_ruby_file 62 | 63 | output = run_ruby_file do 64 | type "bar(Foo)" 65 | type "exit" 66 | end 67 | 68 | assert_include(output, "Object#bar at") 69 | assert_include(output, "Foo.foo at") 70 | assert_include(output, "Foo.foo #=> 100") 71 | assert_include(output, "Object#bar #=> 100") 72 | 73 | # Test that the tracer output does not include IRB's own files 74 | assert_not_include(output, "irb/workspace.rb") 75 | end 76 | 77 | def test_use_tracer_is_disabled_by_default 78 | write_ruby example_ruby_file 79 | 80 | output = run_ruby_file do 81 | type "bar(Foo)" 82 | type "exit" 83 | end 84 | 85 | assert_not_include(output, "#depth:") 86 | assert_not_include(output, "Foo.foo") 87 | end 88 | 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/irb/test_type_completor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Run test only when Ruby >= 3.0 and repl_type_completor is available 4 | return unless RUBY_VERSION >= '3.0.0' 5 | return if RUBY_ENGINE == 'truffleruby' # needs endless method definition 6 | begin 7 | require 'repl_type_completor' 8 | rescue LoadError 9 | return 10 | end 11 | 12 | require 'irb' 13 | require 'tempfile' 14 | require_relative './helper' 15 | 16 | module TestIRB 17 | class TypeCompletorTest < TestCase 18 | DummyContext = Struct.new(:irb_path) 19 | 20 | def setup 21 | ReplTypeCompletor.load_rbs unless ReplTypeCompletor.rbs_loaded? 22 | context = DummyContext.new('(irb)') 23 | @completor = IRB::TypeCompletor.new(context) 24 | end 25 | 26 | def empty_binding 27 | binding 28 | end 29 | 30 | def test_build_completor 31 | IRB.init_config(nil) 32 | verbose, $VERBOSE = $VERBOSE, nil 33 | original_completor = IRB.conf[:COMPLETOR] 34 | workspace = IRB::WorkSpace.new(Object.new) 35 | @context = IRB::Context.new(nil, workspace, TestInputMethod.new) 36 | IRB.conf[:COMPLETOR] = nil 37 | expected_default_completor = RUBY_VERSION >= '3.4' ? 'IRB::TypeCompletor' : 'IRB::RegexpCompletor' 38 | assert_equal expected_default_completor, @context.send(:build_completor).class.name 39 | IRB.conf[:COMPLETOR] = :type 40 | assert_equal 'IRB::TypeCompletor', @context.send(:build_completor).class.name 41 | ensure 42 | $VERBOSE = verbose 43 | IRB.conf[:COMPLETOR] = original_completor 44 | end 45 | 46 | def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil) 47 | raise ArgumentError if include.nil? && exclude.nil? 48 | candidates = @completor.completion_candidates(preposing, target, '', bind: binding) 49 | assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include 50 | assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude 51 | end 52 | 53 | def assert_doc_namespace(preposing, target, namespace, binding: empty_binding) 54 | @completor.completion_candidates(preposing, target, '', bind: binding) 55 | assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding) 56 | end 57 | 58 | def test_type_completion 59 | bind = eval('num = 1; binding') 60 | assert_completion('num.times.map(&:', 'ab', binding: bind, include: 'abs') 61 | assert_doc_namespace('num.chr.', 'upcase', 'String#upcase', binding: bind) 62 | end 63 | 64 | def test_inspect 65 | assert_match(/\AReplTypeCompletor.*\z/, @completor.inspect) 66 | end 67 | 68 | def test_empty_completion 69 | candidates = @completor.completion_candidates('(', ')', '', bind: binding) 70 | assert_equal [], candidates 71 | assert_doc_namespace('(', ')', nil) 72 | end 73 | 74 | def test_command_completion 75 | binding.eval("some_var = 1") 76 | # completion for help command's argument should only include command names 77 | assert_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'show_source') 78 | assert_not_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'some_var') 79 | 80 | assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') 81 | assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') 82 | end 83 | end 84 | 85 | class TypeCompletorIntegrationTest < IntegrationTestCase 86 | def test_type_completor 87 | write_rc <<~RUBY 88 | IRB.conf[:COMPLETOR] = :type 89 | RUBY 90 | 91 | write_ruby <<~'RUBY' 92 | binding.irb 93 | RUBY 94 | 95 | output = run_ruby_file do 96 | type "irb_info" 97 | type "sleep 0.01 until ReplTypeCompletor.rbs_loaded?" 98 | type "completor = IRB.CurrentContext.io.instance_variable_get(:@completor);" 99 | type "n = 10" 100 | type "puts completor.completion_candidates 'a = n.abs;', 'a.b', '', bind: binding" 101 | type "puts completor.doc_namespace 'a = n.chr;', 'a.encoding', '', bind: binding" 102 | type "exit!" 103 | end 104 | assert_match(/Completion: Autocomplete, ReplTypeCompletor/, output) 105 | assert_match(/a\.bit_length/, output) 106 | assert_match(/String#encoding/, output) 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/irb/test_workspace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'tempfile' 3 | require 'irb' 4 | require 'irb/workspace' 5 | require 'irb/color' 6 | 7 | require_relative "helper" 8 | 9 | module TestIRB 10 | class WorkSpaceTest < TestCase 11 | def test_code_around_binding 12 | IRB.conf[:USE_COLORIZE] = false 13 | Tempfile.create('irb') do |f| 14 | code = <<~RUBY 15 | # 1 16 | # 2 17 | IRB::WorkSpace.new(binding) # 3 18 | # 4 19 | # 5 20 | RUBY 21 | f.print(code) 22 | f.close 23 | 24 | workspace = eval(code, binding, f.path) 25 | assert_equal(<<~EOS, without_term { workspace.code_around_binding }) 26 | 27 | From: #{f.path} @ line 3 : 28 | 29 | 1: # 1 30 | 2: # 2 31 | => 3: IRB::WorkSpace.new(binding) # 3 32 | 4: # 4 33 | 5: # 5 34 | 35 | EOS 36 | end 37 | ensure 38 | IRB.conf.delete(:USE_COLORIZE) 39 | end 40 | 41 | def test_code_around_binding_with_existing_unreadable_file 42 | pend 'chmod cannot make file unreadable on windows' if windows? 43 | pend 'skipped in root privilege' if Process.uid == 0 44 | 45 | Tempfile.create('irb') do |f| 46 | code = "IRB::WorkSpace.new(binding)\n" 47 | f.print(code) 48 | f.close 49 | 50 | File.chmod(0, f.path) 51 | 52 | workspace = eval(code, binding, f.path) 53 | assert_equal(nil, workspace.code_around_binding) 54 | end 55 | end 56 | 57 | def test_code_around_binding_with_script_lines__ 58 | IRB.conf[:USE_COLORIZE] = false 59 | with_script_lines do |script_lines| 60 | Tempfile.create('irb') do |f| 61 | code = "IRB::WorkSpace.new(binding)\n" 62 | script_lines[f.path] = code.split(/^/) 63 | 64 | workspace = eval(code, binding, f.path) 65 | assert_equal(<<~EOS, without_term { workspace.code_around_binding }) 66 | 67 | From: #{f.path} @ line 1 : 68 | 69 | => 1: IRB::WorkSpace.new(binding) 70 | 71 | EOS 72 | end 73 | end 74 | ensure 75 | IRB.conf.delete(:USE_COLORIZE) 76 | end 77 | 78 | def test_code_around_binding_on_irb 79 | workspace = eval("IRB::WorkSpace.new(binding)", binding, "(irb)") 80 | assert_equal(nil, workspace.code_around_binding) 81 | end 82 | 83 | def test_toplevel_binding_local_variables 84 | bug17623 = '[ruby-core:102468]' 85 | bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] 86 | top_srcdir = "#{__dir__}/../.." 87 | reline_libdir = Gem.loaded_specs["reline"].full_gem_path + "/lib" 88 | irb_path = nil 89 | %w[exe libexec].find do |dir| 90 | irb_path = "#{top_srcdir}/#{dir}/irb" 91 | File.exist?(irb_path) 92 | end or omit 'irb command not found' 93 | assert_in_out_err(bundle_exec + ['-W0', "-I#{top_srcdir}/lib", "-I#{reline_libdir}", "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623) 94 | version = 'xyz' # typical rubygems loading file 95 | load('#{irb_path}') 96 | RUBY 97 | end 98 | 99 | private 100 | 101 | def with_script_lines 102 | script_lines = nil 103 | debug_lines = {} 104 | Object.class_eval do 105 | if defined?(SCRIPT_LINES__) 106 | script_lines = SCRIPT_LINES__ 107 | remove_const :SCRIPT_LINES__ 108 | end 109 | const_set(:SCRIPT_LINES__, debug_lines) 110 | end 111 | yield debug_lines 112 | ensure 113 | Object.class_eval do 114 | remove_const :SCRIPT_LINES__ 115 | const_set(:SCRIPT_LINES__, script_lines) if script_lines 116 | end 117 | end 118 | 119 | def without_term 120 | env = ENV.to_h.dup 121 | ENV.delete('TERM') 122 | yield 123 | ensure 124 | ENV.replace(env) 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | 6 | module Test 7 | module Unit 8 | class TestCase 9 | def windows? platform = RUBY_PLATFORM 10 | /mswin|mingw/ =~ platform 11 | end 12 | end 13 | end 14 | end 15 | --------------------------------------------------------------------------------