[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches:\n      - master\n      - test\n  pull_request:\n    branches:\n      - master\n      - test\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        emacs_version:\n          - 28.2\n          - 29.1\n\n    steps:\n      - name: Set up Emacs\n        uses: purcell/setup-emacs@master\n        with:\n          version: ${{matrix.emacs_version}}\n\n      - name: Check out project\n        uses: actions/checkout@v3\n\n      - name: Version Info\n        run: |\n          ruby -v\n          gem -v\n\n      - name: Test\n        run: rake test:all\n"
  },
  {
    "path": ".gitignore",
    "content": ".bzr\n.bzrignore\n*~\nnokoload.gemspec\npkg\ntmp\n*.elc"
  },
  {
    "path": "COPYING",
    "content": "Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.\nYou can redistribute it and/or modify it under either the terms of the GPL\nversion 2 (see the file GPL), or the conditions below:\n\n  1. You may make and give away verbatim copies of the source form of the\n     software without restriction, provided that you duplicate all of the\n     original copyright notices and associated disclaimers.\n\n  2. You may modify your copy of the software in any way, provided that\n     you do at least ONE of the following:\n\n       a) place your modifications in the Public Domain or otherwise\n          make them Freely Available, such as by posting said\n\t  modifications to Usenet or an equivalent medium, or by allowing\n\t  the author to include your modifications in the software.\n\n       b) use the modified software only within your corporation or\n          organization.\n\n       c) give non-standard binaries non-standard names, with\n          instructions on where to get the original software distribution.\n\n       d) make other distribution arrangements with the author.\n\n  3. You may distribute the software in object code or binary form,\n     provided that you do at least ONE of the following:\n\n       a) distribute the binaries and library files of the software,\n\t  together with instructions (in the manual page or equivalent)\n\t  on where to get the original distribution.\n\n       b) accompany the distribution with the machine-readable source of\n\t  the software.\n\n       c) give non-standard binaries non-standard names, with\n          instructions on where to get the original software distribution.\n\n       d) make other distribution arrangements with the author.\n\n  4. You may modify and include the part of the software into any other\n     software (possibly commercial).  But some files in the distribution\n     are not written by the author, so that they are not under these terms.\n\n     For the list of those files and their copying conditions, see the\n     file LEGAL.\n\n  5. The scripts and library files supplied as input to or produced as \n     output from the software do not automatically fall under the\n     copyright of the software, but belong to whomever generated them, \n     and may be sold commercially, and may be aggregated with this\n     software.\n\n  6. THIS SOFTWARE IS PROVIDED \"AS IS\" AND WITHOUT ANY EXPRESS OR\n     IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED\n     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n     PURPOSE.\n"
  },
  {
    "path": "README.rdoc",
    "content": "= Enhanced Ruby Mode\n\n* Git: http://github.com/zenspider/Enhanced-Ruby-Mode\n* Author: Geoff Jacobsen / forked by Ryan Davis\n* Copyright: 2010 - 2012\n* License: RUBY License\n\n== Description\n\nEnhanced Ruby Mode replaces the emacs ruby mode that comes with ruby.\n\nIt uses the Ripper class found in ruby 1.9.2 (and later) to parse and indent the source code.  As a consequence only ruby 1.9.2 (or later) syntax is parsed correctly.\n\nSyntax checking is also performed.\n\n== TODO\n\n* Optimisation; currently parses and fontifies whole buffer for most modifications - it still appears to run fast enough on large files.\n\n* Suggestions?\n\n\n== Synopsis\n\n* Enhanced Ruby Mode is installable via el-get and Melpa, where its package name is +enh-ruby-mode+.\n\n* For manual installation, add the following file to your init file.\n\n   (add-to-list 'load-path \"(path-to)/Enhanced-Ruby-Mode\") ; must be added after any path containing old ruby-mode\n   (autoload 'enh-ruby-mode \"enh-ruby-mode\" \"Major mode for ruby files\" t)\n   (add-to-list 'auto-mode-alist '(\"\\\\.rb\\\\'\" . enh-ruby-mode))\n   (add-to-list 'interpreter-mode-alist '(\"ruby\" . enh-ruby-mode))\n\n   ;; optional\n\n   (setq enh-ruby-program \"(path-to-ruby1.9)/bin/ruby\") ; so that still works if ruby points to ruby1.8\n\n* Enhanced Ruby Mode defines its own specific faces with the hook <tt>erm-define-faces</tt>. If your theme is already defining those faces, to not overwrite them, just remove the hook with:\n\n   (remove-hook 'enh-ruby-mode-hook 'erm-define-faces)\n\n== Existing ruby-mode hooks\n\nYou may have existing lines in your emacs config that add minor modes based on ruby mode, like this:\n    (add-hook 'ruby-mode-hook 'robe-mode)\n    (add-hook 'ruby-mode-hook 'yard-mode)\n\nFor these to work with enh-ruby-mode, you need to add hooks to the enh-ruby-mode minor mode:\n    (add-hook 'enh-ruby-mode-hook 'robe-mode)\n    (add-hook 'enh-ruby-mode-hook 'yard-mode)\n\n== Load enh-ruby-mode for Ruby files\n\nTo use enh-ruby-mode for <tt>.rb</tt> add the following to your init file:\n    (add-to-list 'auto-mode-alist '(\"\\\\.rb\\\\'\" . enh-ruby-mode))\n\nTo use enh-ruby-mode for all common Ruby files and the following to your init file:\n    (add-to-list 'auto-mode-alist\n                 '(\"\\\\(?:\\\\.rb\\\\|ru\\\\|rake\\\\|thor\\\\|jbuilder\\\\|gemspec\\\\|podspec\\\\|/\\\\(?:Gem\\\\|Rake\\\\|Cap\\\\|Thor\\\\|Vagrant\\\\|Guard\\\\|Pod\\\\)file\\\\)\\\\'\" . enh-ruby-mode))\n\n== Requirements\n\n* ruby 1.9.2 (or later)\n\n== Install\n\n* git clone git@github.com:zenspider/Enhanced-Ruby-Mode.git\n\n== Development\n\nDeveloping requires minitest 5.x gem.\n\nTesting parser:\n\n    rake test:ruby [N=name or /pattern/]\n\n    rake test:elisp [N=pattern]\n\n    rake test:all\n\n    rake                            # same as test:all\n\nTests for Emacs Lisp require ERT. It is built-in since Emacs 24.1.\n\n== Credits\n\nJell (Jean-Louis Giordano) https://github.com/Jell\n  Improved UTF-8 support\n"
  },
  {
    "path": "Rakefile",
    "content": "task :default => %w[clean compile test:all]\n\nel_files = Rake::FileList['**/enh-ruby-mode*.el']\n\ndef run cmd\n  sh cmd do |good|\n    # block prevents ruby backtrace on failure\n    exit 1 unless good\n  end\nend\n\ndef emacs args\n  emacs_cmd = Dir[\n    \"/usr/local/bin/emacs\",\n    \"/{My,}Applications/Emacs.app/Contents/MacOS/Emacs\" # homebrew\n  ].first || \"emacs\" # trust the path\n\n  run %Q[#{emacs_cmd} -Q -L . #{args}]\nend\n\ndef emacs_test args\n  emacs \"-l enh-ruby-mode-test.el #{args}\"\nend\n\ndesc \"byte compile the project. Helps drive out warnings, but also faster.\"\ntask compile: el_files.ext('.elc')\n\nrule '.elc' => '.el' do |t|\n  emacs \"--batch -f batch-byte-compile #{t.source}\"\nend\n\ndesc \"Clean the project\"\ntask :clean do\n  rm_f Dir[\"**/*~\", \"**/*.elc\"]\nend\n\ntask :test => %w[ test:ruby test:elisp ]\n\nnamespace :test do\n  desc \"Run tests for Ruby\"\n  task :ruby do\n    n = ENV[\"N\"]\n\n    if n then\n      run %Q[ruby -wI. test/test_erm_buffer.rb -n #{n.dump}]\n    else\n      run %Q[ruby -wI. test/test_erm_buffer.rb]\n    end\n  end\n\n  desc \"Run tests for Emacs Lisp\"\n  task :elisp do\n    n=ENV[\"N\"]\n\n    Dir.chdir \"test\" do\n      if n then\n        emacs_test \"--batch -eval '(ert-run-tests-batch-and-exit #{n.dump})'\"\n      else\n        emacs_test \"--batch -f ert-run-tests-batch-and-exit\"\n      end\n    end\n  end\n\n  desc \"Run tests for Emacs Lisp interactively\"\n  task :elispi do\n    Dir.chdir \"test\" do\n      emacs_test %q[-eval \"(ert-run-tests-interactively 't)\"]\n    end\n  end\n\n  desc \"Run test:ruby and test:elisp\"\n  task :all => [:ruby, :elisp]\nend\n\ndef docker cmd\n  sh %(docker run -v $PWD:/erm --rm -i -t -w /erm/test zenspider/emacs-ruby #{cmd})\nend\n\ndesc \"test in a docker container\"\ntask :docker do\n  docker \"rake test:all\"\nend\n\ndesc \"interactive test in a docker container\"\ntask :dockeri do\n  docker \"rake test:elispi\"\nend\n\ndesc \"run a shell in a docker container\"\ntask :sh do\n  docker \"/bin/sh\"\nend\n\ndesc \"debug a file (F=path)\"\ntask :debug do\n  f = ENV[\"F\"]\n  system \"ruby tools/debug.rb #{f}\"\n  puts\n  system \"ruby tools/lexer.rb #{f}\"\n  puts\n  system \"ruby tools/markup.rb #{f}\"\nend\n"
  },
  {
    "path": "debugging.md",
    "content": "# How to Debug Problems in ERM\n\nThese are notes to myself because I don't work on this project much.\n\n## 0. Run `rake docker` or `rake dockeri` to run tests in isolation.\n\nMake sure that everything else is currently good before you go\ndebugging new issues.\n\n```\nrm *.elc; cmacs -Q -l loader.el  wtf.rb\n```\n\n## 1. First, get a reproduction in a file named bug###.rb.\n\nThis helps you track back to a github issue (create one if necessary).\n\n## 2. Reduce the reproduction to the bare minimum.\n\nUsually, there's little need for \"real\" code and the submission\ncontains a lot of sub-expressions that can be removed.\n\n```ruby\nrenewed_clients = @renewed_clients_ransack\n  .result\n    .order('due_at desc')\n    .page(params[:renewed_clients_page])\n```\n\nvs:\n\n```ruby\n@b\n  .c\n    .d\n```\n\nThere's no need for arguments or the extra calls. Even the assignment\ncan be removed.\n\nKnow what the expected result should be. In the case of the above, the\nindentation is off and should be:\n\n```ruby\n@b\n  .c\n  .d\n```\n\n## 3. Run `rake debug F=bug###.rb` to get relevant output.\n\nThis outputs what it sees, not what it thinks it should be. For the\nabove, the output looks like (with notes inline):\n\n### 3.1. tools/debug.rb:\n\n```ruby\n[:ivar, \"@b\", 2]\n[:sp, \"\\n\", 1]\n[:sp, \"  \", 2]\n[:rem, \".\", 1]\n[:ident, \"c\", 1]\n[:sp, \"\\n\", 1]\n[:sp, \"    \", 4]\n[:indent, :c, -4]\n[:rem, \".\", 1]\n[:ident, \"d\", 1]\n((15 1 16 c 9)(0 3 16)(3 1 3))\n```\n\nThis is a raw printing of the tokens as they are lexed in triplets of\ntoken type, token value, and length. It is followed with the data that\nactually goes back from the ruby process to emacs. This is what is\nused to highlight and/or indent.\n\nTODO: I don't know how to read that sexp yet. But I want to document\nall of this output first to see if it knocks something loose.\n\n### 3.2. tools/lexer.rb:\n\nThis tool is helpful because it only uses Ripper and knows nothing\nabout ERM.\n\nThis is the raw output from Ripper.lex and is basically the events\nthat will be triggered in ERM:\n\n```ruby\n[[[1, 0], :on_ivar, \"@b\", EXPR_END],\n [[1, 2], :on_ignored_nl, \"\\n\", EXPR_END],\n [[2, 0], :on_sp, \"  \", EXPR_END],\n [[2, 2], :on_period, \".\", EXPR_DOT],\n [[2, 3], :on_ident, \"c\", EXPR_ARG],\n [[2, 4], :on_ignored_nl, \"\\n\", EXPR_ARG],\n [[3, 0], :on_sp, \"    \", EXPR_ARG],\n [[3, 4], :on_period, \".\", EXPR_DOT],\n [[3, 5], :on_ident, \"d\", EXPR_ARG],\n [[3, 6], :on_nl, \"\\n\", EXPR_BEG]]\n```\n\nThis is the raw output from Ripper.sexp_raw:\n\n```ruby\n[:program,\n [:stmts_add,\n  [:stmts_new],\n  [:call,\n   [:call, [:var_ref, [:@ivar, \"@b\", [1, 0]]], :\".\", [:@ident, \"c\", [2, 3]]],\n   :\".\",\n   [:@ident, \"d\", [3, 5]]]]]\n```\n\nThis is the raw output from Ripper.sexp and is basically the same\nthing but cleaned up / combined a bit:\n\n```ruby\n[:program,\n [[:call,\n   [:call, [:var_ref, [:@ivar, \"@b\", [1, 0]]], :\".\", [:@ident, \"c\", [2, 3]]],\n   :\".\",\n   [:@ident, \"d\", [3, 5]]]]]\n```\n\n### 3.3. tools/markup.rb\n\n```\n((15 1 16 c 9)(0 3 16)(3 1 3))\n---\n«3»@b«0»\n  .c\n«@c»    .d\n```\n\nThe sexp, roughly described is:\n\n```ruby\n((code.size, 1, code.size+1, indent_stack.join) result.join)\n```\n\nTODO: I'm not sure how to read result yet.\n\n## 4. See if you can find the closest passing version of the repro\n\nIn this example, if the receiver is not an ivar, it works fine:\n\n```ruby\nb\n  .c\n  .d\n```\n\n## 5. Get both passing and repro into tests\n\nThis allows you some freedom. At this point, everything is isolated\nand reproducible. You should see that one passes and the other one fails.\n\n```lisp\n(ert-deftest enh-ruby-indent-leading-dots-ident ()\n  (with-temp-enh-rb-string\n   \"b\\n.c\\n.d\\n\"\n\n   (indent-region (point-min) (point-max))\n   (buffer-should-equal \"b\\n  .c\\n  .d\\n\")))\n\n(ert-deftest enh-ruby-indent-leading-dots-ivar ()\n  (with-temp-enh-rb-string\n   \"@b\\n.c\\n.d\\n\"\n\n   (indent-region (point-min) (point-max))\n   (buffer-should-equal \"@b\\n  .c\\n  .d\\n\")))\n```\n\n## 6. Try to ferret out the difference.\n\nI was able to do that with:\n\n```\n% ruby tools/debug.rb --trace bug128_1.rb | nopwd > 1\n% ruby tools/debug.rb --trace bug128_2.rb | nopwd > 2\n```\n\nand then using `ediff` to look at the differences in execution paths.\n\nIn particular, I could see that a major difference down the line\ndepended on whether `@ident` was true:\n\n```diff\n #0:./ruby/erm_buffer.rb:19:ErmBuffer::Adder:-:         case sym\n-#0:./ruby/erm_buffer.rb:23:ErmBuffer::Adder:-:           @ident = false\n+#0:./ruby/erm_buffer.rb:21:ErmBuffer::Adder:-:           @ident = true\n #0:./ruby/erm_buffer.rb:27:ErmBuffer::Adder:-:       @first_token = ft\n```\n\nfollowed by:\n\n```diff\n #0:./ruby/erm_buffer.rb:487:ErmBuffer::Parser:-:       if @ident\n+#0:./ruby/erm_buffer.rb:488:ErmBuffer::Parser:-:         line_so_far_str = @line_so_far.map {|a| a[1] }.join\n+#0:./ruby/erm_buffer.rb:489:ErmBuffer::Parser:-:         if line_so_far_str.strip == \"\"\n+#0:./ruby/erm_buffer.rb:490:ErmBuffer::Parser:-:           indent :c, (line_so_far_str.length * -1)\n+#0:./ruby/erm_buffer.rb:99:ErmBuffer::Parser:>:     def indent type, c = 0\n...\n```\n\nThis was the major difference between the two and made it easy to\nreason about.\n\n## 7. Make the test pass\n\nChanging from:\n\n```ruby\n  when :ident, :const then\n```\n\nto\n\n```ruby\n  when :ident, :const, :ivar, :gvar, :cvar then\n```\n\n(with 2 extra tests) made the tests pass and things seem happier.\n\n# Profiling\n\nmisc dump for now:\n\nhttps://github.com/zenspider/enhanced-ruby-mode/issues/171\n\n```elisp\n(with-current-buffer \"big_file.rb\"\n  (profiler-start 'cpu)\n  (--dotimes 10 (self-insert-command 1 ?s))\n  (profiler-report)\n  (profiler-stop))\n```\n\nhttps://github.com/zenspider/enhanced-ruby-mode/issues/146\n\nprofiling electric-indent-mode vs not:\n\n```elisp\n(with-current-buffer \"ruby25_parser.rb\"\n  (goto-char (point-max))\n  (electric-indent-mode (if electric-indent-mode -1 1))\n\n  (profiler-start 'cpu)\n  (--dotimes 10 (call-interactively 'newline))\n  (profiler-report)\n  (profiler-stop))\n```\n\nversus electric-indent-mode under text-mode:\n\n```elisp\n(with-current-buffer \"ruby25_parser.rb\"\n  (goto-char (point-max))\n  (text-mode)\n  (setq start (float-time))\n  (electric-indent-mode (if electric-indent-mode -1 1))\n  (enh-ruby-mode)\n  (erm-wait-for-parse)\n  (message \"%f\" (- (float-time) start)))\n```\n\nfor testing N large operations across a file/buffer\n```elisp\n(progn\n  (profiler-start 'cpu)\n  (--dotimes 20\n    (message \"attempt %d\" it)\n    (let ((buf (find-file \"lib/ruby27_parser.rb\")))\n      (with-current-buffer buf\n        (enh-ruby-mode)\n        ;; (erm-wait-for-parse)\n\n        (goto-char (point-max))\n\n        (--dotimes 10 (call-interactively 'newline))\n\n        (erm-wait-for-parse)\n\n        (set-buffer-modified-p nil)\n        (kill-buffer buf))))\n  (profiler-report)\n  (profiler-stop))\n```\n\nfor profiling N operations on an open buffer and reverting any changes made:\n```elisp\n(with-current-buffer \"ruby25_parser.rb\"\n  (goto-char (point-max))\n\n  ;; (electric-indent-mode (if electric-indent-mode -1 1))\n  (electric-indent-mode -1)\n  ;; (electric-indent-mode 1)\n\n  ;; (erm-wait-for-parse)\n  ;; (message \"starting\")\n  (profiler-start 'cpu)\n\n  (--dotimes 10 (call-interactively 'newline))\n  ;; (erm-wait-for-parse)\n\n  (profiler-report)\n  (profiler-stop)\n\n  (with-current-buffer \"ruby25_parser.rb\"\n    (erm-wait-for-parse)\n    (revert-buffer nil t))\n  )\n```\n"
  },
  {
    "path": "enh-ruby-mode.el",
    "content": ";;; enh-ruby-mode.el --- Major mode for editing Ruby files  -*- lexical-binding: t; -*-\n\n;; Copyright (C) 2012-2022+ -- Ryan Davis\n;; Copyright (C) 2010-2012 Geoff Jacobsen\n\n;; Author: Geoff Jacobsen\n;; Maintainer: Ryan Davis\n;; URL: https://github.com/zenspider/Enhanced-Ruby-Mode\n;; Created: Sep 18 2010\n;; Keywords: languages, elisp, ruby\n;; Package-Requires: ((emacs \"25.1\"))\n;; Version: 1.2.0\n\n;; This file is not part of GNU Emacs.\n\n;; This file is free software: you can redistribute it and/or modify\n;; it under the terms of the GNU General Public License as published by\n;; the Free Software Foundation, either version 2 of the License, or\n;; (at your option) any later version.\n\n;; It is distributed in the hope that it will be useful, but WITHOUT\n;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public\n;; License for more details.\n\n;; You should have received a copy of the GNU General Public License\n;; along with it.  If not, see <http://www.gnu.org/licenses/>.\n\n;;; Commentary:\n\n;; This is a fork of https://github.com/jacott/Enhanced-Ruby-Mode\n;; to provide further enhancements and bug fixes.\n;;\n;; It has been renamed to enh-ruby-mode.el to avoid name conflicts\n;; with ruby-mode that ships with Emacs. All symbols that started with\n;; 'ruby now start with 'enh-ruby. This also makes it possible to\n;; switch back and forth for testing purposes.\n\n;; Provides fontification, indentation, syntax checking, and navigation for Ruby code.\n;;\n;; If you're installing manually, you should add this to your .emacs\n;; file after putting it on your load path:\n;;\n;;    (add-to-list 'load-path \"(path-to)/Enhanced-Ruby-Mode\") ; must be added after any path containing old ruby-mode\n;;    (setq enh-ruby-program \"(path-to-ruby)/bin/ruby\") ; so that still works if ruby points to ruby1.8\n;;\n\n(require 'cl-lib)                       ; for cdddr, caddr\n(require 'files)                        ; for mode-require-final-newline\n(require 'paren)                        ; show-paren-data-function\n(require 'seq)                          ; seq-remove, seq-difference\n(require 'subr-x)                       ; string-trim-right\n(require 'cus-edit)                     ; custom-variable-state\n(require 'find-func)                    ; find-library-name\n\n;;; Properties & Other Bullshit Codes:\n\n;; 'l - [, (, {, %w/%i open  or | goalpost open\n;; 'r - ], ), }, %w/%i close or | goalpost close\n;; 'b - begin, def, case, if\n;; 'd - do, {, embexpr (interpolation) start\n;; 'e - end, embexpr (interpolation) end, close block }\n;; 's - statement start on BACKDENT_KW else/when/rescue etc\n;; 'c - continue - period followed by return (or other way around?)\n\n;; pc\n;; bc\n;; nbc\n;; npc\n\n;;; Variables:\n\n(defcustom enh-ruby-add-encoding-comment-on-save nil\n  \"Adds ruby magic encoding comment on save when non-nil.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-bounce-deep-indent nil\n  \"Bounce between normal indentation and deep indentation when non-nil.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-check-syntax 'errors-and-warnings\n  \"Highlight syntax errors and warnings.\"\n  :type '(radio (const :tag \"None\" nil)\n                (const :tag \"Errors\" errors)\n                (const :tag \"Errors and warnings\" errors-and-warnings))\n  :safe #'enh-symbol-or-null-p\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-comment-column 32\n  \"*Indentation column of comments.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-deep-indent-construct t\n  \"*Deep indent constructs such as if, def, class and module when non-nil.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-deep-indent-paren t\n  \"*Deep indent lists in parenthesis when non-nil.\"\n  ;; FIX: this applies to square brackets as well\n  :type 'boolean\n  :safe  #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-encoding-map\n  '((us-ascii       . nil)       ;; Do not put coding: us-ascii\n    (utf-8          . nil)       ;; Do not put coding: utf-8\n    (shift-jis      . cp932)     ;; Emacs charset name of Shift_JIS\n    (shift_jis      . cp932)     ;; MIME charset name of Shift_JIS\n    (japanese-cp932 . cp932))    ;; Emacs charset name of CP932\n  \"Alist to map encoding name from Emacs to ruby.\"\n  :type '(alist :key-type   (symbol :tag \"Encoding\")\n                :value-type (choice (const :tag \"Ignore\" nil)\n                                    (symbol :tag \"Charset\")))\n  :safe (lambda (xs)\n          (and (listp xs)\n           (cl-every (lambda (x)\n                       (and (symbolp (car x))\n                            (enh-symbol-or-null-p (cdr x))))\n                     xs)))\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-extra-keywords nil\n  \"*A list of idents that will be fontified as keywords.\n\n`erm-reset' will need to be called in order for any global\nchanges to take effect.\n\nThis variable can also be buffer local in which case it will\noverride the global value for the buffer it is local\nto. `ruby-local-enable-extra-keywords' needs to be called after\nthe value changes.\"\n  :type '(repeat string)\n  :safe #'listp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-hanging-brace-deep-indent-level 0\n  \"*Extra hanging deep indentation for continued ruby curly or square braces.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-hanging-brace-indent-level 2\n  \"*Extra hanging indentation for continued ruby curly braces.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-hanging-indent-level 2\n  \"*Extra hanging Indentation for continued ruby statements.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-hanging-paren-deep-indent-level 0\n  \"*Extra hanging deep indentation for continued ruby parenthesis.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-hanging-paren-indent-level 2\n  \"*Extra hanging indentation for continued ruby parenthesis.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-indent-level 2\n  \"*Indentation of ruby statements.\"\n  :type 'integer\n  :safe #'integerp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-indent-tabs-mode nil\n  \"*Indentation can insert tabs in ruby mode if this is non-nil.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-preserve-indent-in-heredocs nil\n  \"Indent heredocs and multiline strings like ‘text-mode’.\n\nWarning: does not play well with command ‘electric-indent-mode’.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-program \"ruby\"\n  \"The ruby program to parse the source.\"\n  :type 'string\n  :safe #'stringp\n  :group 'enh-ruby)\n\n(defcustom enh-ruby-use-encoding-map t\n  \"*Use `enh-ruby-encoding-map' to set encoding magic comment if this is non-nil.\"\n  :type 'boolean\n  :safe #'booleanp\n  :group 'enh-ruby)\n\n(defvar enh-ruby-use-ruby-mode-show-parens-config nil\n  \"This flag has no effect anymore as ERM supports command\n‘show-paren-mode’ directly.\")\n\n(make-obsolete-variable 'enh-ruby-use-ruby-mode-show-parens-config nil \"2018-04-03\")\n\n;; TODO: renames:\n;;\n;; enh-ruby-indent-level:                    2\n;; enh-ruby-hanging-indent-level:            2\n;; enh-ruby-hanging-brace-deep-indent-level: 0\n;; enh-ruby-hanging-brace-indent-level:      2\n;; enh-ruby-hanging-paren-deep-indent-level: 0\n;; enh-ruby-hanging-paren-indent-level:      2\n;;\n;; Versus:\n;;\n;; enh-ruby-indent-level:                    2\n;; enh-ruby-indent-level-hanging:            2\n;; enh-ruby-indent-level-hanging-paren:      2\n;; enh-ruby-indent-level-hanging-paren-deep: 0\n;; enh-ruby-indent-level-hanging-brace:      2\n;; enh-ruby-indent-level-hanging-brace-deep: 0\n\n(defvar need-syntax-check-p)\n(defvar erm-buff-num)\n(defvar erm-e-w-status)\n(defvar erm-full-parse-p)\n\n;;; Constants\n\n(defconst enh-ruby-block-end-re \"\\\\_<end\\\\_>\")\n\n(defconst enh-ruby-symbol-chars \"a-zA-Z0-9_=?!\")\n\n(defconst enh-ruby-symbol-re (concat \"[\" enh-ruby-symbol-chars \"]\"))\n\n(defconst enh-ruby-defun-beg-keywords\n  '(\"class\" \"module\" \"def\")\n  \"Keywords at the beginning of definitions.\")\n\n(defconst enh-ruby-defun-beg-re\n  (regexp-opt enh-ruby-defun-beg-keywords)\n  \"Regexp to match the beginning of definitions.\")\n\n(defconst enh-ruby-defun-and-name-re\n  (concat \"\\\\(\" enh-ruby-defun-beg-re \"\\\\)[ \\t]+\\\\(\"\n                                         ;; \\\\. and :: for class method\n                                         \"\\\\([A-Za-z_]\" enh-ruby-symbol-re \"*\\\\|\\\\.\\\\|::\" \"\\\\)\"\n                                         \"+\\\\)\")\n  \"Regexp to match definitions and their name.\")\n\n(defconst erm-process-delimiter\n  \"\\n\\0\\0\\0\\n\")\n\n(define-abbrev-table 'enh-ruby-mode-abbrev-table ()\n  \"Abbrev table used by enhanced-ruby-mode.\")\n\n(define-abbrev enh-ruby-mode-abbrev-table \"end\" \"end\"\n  #'indent-for-tab-command :system t)\n\n(defvar enh-ruby-mode-map\n  (let ((map (make-sparse-keymap)))\n    (define-key map \"{\"             #'enh-ruby-electric-brace)\n    (define-key map \"}\"             #'enh-ruby-electric-brace)\n    (define-key map (kbd \"M-C-a\")   #'enh-ruby-beginning-of-defun)\n    (define-key map (kbd \"M-C-e\")   #'enh-ruby-end-of-defun)\n    (define-key map (kbd \"M-C-b\")   #'enh-ruby-backward-sexp)\n    (define-key map (kbd \"M-C-f\")   #'enh-ruby-forward-sexp)\n    (define-key map (kbd \"M-C-p\")   #'enh-ruby-beginning-of-block)\n    (define-key map (kbd \"M-C-n\")   #'enh-ruby-end-of-block)\n    (define-key map (kbd \"M-C-h\")   #'enh-ruby-mark-defun)\n    (define-key map (kbd \"M-C-q\")   #'enh-ruby-indent-exp)\n    (define-key map (kbd \"C-c C-f\") #'enh-ruby-find-file)\n    (define-key map (kbd \"C-c C-e\") #'enh-ruby-find-error)\n    (define-key map (kbd \"C-c /\")   #'enh-ruby-insert-end)\n    (define-key map (kbd \"C-c {\")   #'enh-ruby-toggle-block)\n    (define-key map (kbd \"M-C-u\")   #'enh-ruby-up-sexp)\n    (define-key map (kbd \"C-j\")     #'reindent-then-newline-and-indent)\n    map)\n  \"Syntax table in use in ‘enh-ruby-mode’ buffers.\")\n\n(defvar enh-ruby-mode-syntax-table\n  (let ((table (make-syntax-table)))\n    (modify-syntax-entry ?\\' \"\\\"\" table)\n    (modify-syntax-entry ?\\\" \"\\\"\" table)\n    (modify-syntax-entry ?\\` \"\\\"\" table)\n    (modify-syntax-entry ?#  \"<\"  table)\n    (modify-syntax-entry ?\\n \">\"  table)\n    (modify-syntax-entry ?\\\\ \"\\\\\" table)\n    (modify-syntax-entry ?$  \"'\"  table)\n    (modify-syntax-entry ??  \"_\"  table)\n    (modify-syntax-entry ?_  \"_\"  table)\n    (modify-syntax-entry ?:  \"'\"  table)\n    (modify-syntax-entry ?<  \".\"  table)\n    (modify-syntax-entry ?>  \".\"  table)\n    (modify-syntax-entry ?&  \".\"  table)\n    (modify-syntax-entry ?|  \".\"  table)\n    (modify-syntax-entry ?%  \".\"  table)\n    (modify-syntax-entry ?=  \".\"  table)\n    (modify-syntax-entry ?/  \".\"  table)\n    (modify-syntax-entry ?+  \".\"  table)\n    (modify-syntax-entry ?*  \".\"  table)\n    (modify-syntax-entry ?-  \".\"  table)\n    (modify-syntax-entry ?\\; \".\"  table)\n    (modify-syntax-entry ?\\( \"()\" table)\n    (modify-syntax-entry ?\\) \")(\" table)\n    (modify-syntax-entry ?\\{ \"(}\" table)\n    (modify-syntax-entry ?\\} \"){\" table)\n    (modify-syntax-entry ?\\[ \"(]\" table)\n    (modify-syntax-entry ?\\] \")[\" table)\n    (modify-syntax-entry ?@  \"'\"  table)\n\n    table)\n  \"Syntax table used by ‘enh-ruby-mode’ buffers.\")\n\n(defconst enh-ruby-font-lock-keyword-beg-re \"\\\\(?:^\\\\|[^.@$:]\\\\|\\\\.\\\\.\\\\)\")\n\n(defconst enh-ruby-font-lock-keywords\n  `(;; Core methods that have required arguments.\n    (,(concat\n       enh-ruby-font-lock-keyword-beg-re\n       (regexp-opt\n        '( ;; built-in methods on Kernel TODO: add more via reflection?\n          \"at_exit\"\n          \"autoload\"\n          \"autoload?\"\n          \"callcc\"\n          \"catch\"\n          \"eval\"\n          \"exec\"\n          \"format\"\n          \"lambda\"\n          \"load\"\n          \"loop\"\n          \"open\"\n          \"p\"\n          \"print\"\n          \"printf\"\n          \"proc\"\n          \"putc\"\n          \"puts\"\n          \"require\"\n          \"require_relative\"\n          \"spawn\"\n          \"sprintf\"\n          \"syscall\"\n          \"system\"\n          \"throw\"\n          \"trace_var\"\n          \"trap\"\n          \"untrace_var\"\n          \"warn\"\n          ;; keyword-like private methods on Module\n          \"alias_method\"\n          \"attr\"\n          \"attr_accessor\"\n          \"attr_reader\"\n          \"attr_writer\"\n          \"define_method\"\n          \"extend\"\n          \"include\"\n          \"module_function\"\n          \"prepend\"\n          \"private_class_method\"\n          \"private_constant\"\n          \"public_class_method\"\n          \"public_constant\"\n          \"refine\"\n          \"using\")\n        'symbols))\n     (1 (unless (looking-at \" *\\\\(?:[]|,.)}=]\\\\|$\\\\)\")\n          font-lock-builtin-face)))\n    ;; Kernel methods that have no required arguments.\n    (,(concat\n       enh-ruby-font-lock-keyword-beg-re\n       (regexp-opt\n        '(\"__callee__\"\n          \"__dir__\"\n          \"__method__\"\n          \"abort\"\n          \"binding\"\n          \"block_given?\"\n          \"caller\"\n          \"exit\"\n          \"exit!\"\n          \"fail\"\n          \"fork\"\n          \"global_variables\"\n          \"local_variables\"\n          \"private\"\n          \"protected\"\n          \"public\"\n          \"raise\"\n          \"rand\"\n          \"readline\"\n          \"readlines\"\n          \"sleep\"\n          \"srand\")\n        'symbols))\n     (1 font-lock-builtin-face)))\n  \"Additional expressions to highlight in ‘enh-ruby-mode’.\")\n\n(defconst enh-ruby-font-names\n  '(nil\n    font-lock-string-face\n    font-lock-type-face\n    font-lock-variable-name-face\n    font-lock-comment-face\n    font-lock-constant-face\n    font-lock-string-face\n    enh-ruby-string-delimiter-face\n    enh-ruby-regexp-delimiter-face\n    font-lock-function-name-face\n    font-lock-keyword-face\n    enh-ruby-heredoc-delimiter-face\n    enh-ruby-op-face\n    enh-ruby-regexp-face)\n  \"Font faces used by ‘enh-ruby-mode’.\")\n\n;;; Code:\n\n;;;###autoload\n(defun enh-symbol-or-null-p (x)\n  \"Return true if X is either a symbol or null. Used for defcustom safe check.\"\n  (or (symbolp x)\n      (null x)))\n\n(define-obsolete-variable-alias 'enh/symbol-or-null-p\n                                'enh-symbol-or-null-p \"2022-07-07\")\n\n;;;###autoload\n(define-derived-mode enh-ruby-mode prog-mode \"EnhRuby\"\n  \"Enhanced Major mode for editing Ruby code.\n\n\\\\{enh-ruby-mode-map}\"\n\n  (setq-local comment-column               enh-ruby-comment-column)\n  (setq-local comment-end                  \"\")\n  (setq-local comment-start                \"#\")\n  (setq-local comment-start-skip           \"#+ *\")\n  (setq-local erm-buff-num                 nil)\n  (setq-local erm-e-w-status               nil)\n  (setq-local erm-full-parse-p             nil)\n  (setq-local indent-line-function         #'enh-ruby-indent-line)\n  ;; (setq-local forward-sexp-function        #'enh-ruby-forward-sexp)\n  (setq-local need-syntax-check-p          nil)\n  (setq-local paragraph-ignore-fill-prefix t)\n  (setq-local parse-sexp-ignore-comments   t)\n  (setq-local parse-sexp-lookup-properties t)\n  (setq-local require-final-newline        mode-require-final-newline)\n  (setq-local beginning-of-defun-function  #'enh-ruby-beginning-of-defun)\n  (setq-local end-of-defun-function        #'enh-ruby-end-of-defun)\n  (setq-local show-paren-data-function     #'erm-show-paren-data-function)\n  (setq-local paragraph-start              (concat \"$\\\\|\" page-delimiter))\n  (setq-local paragraph-separate           paragraph-start)\n\n  (setq-local add-log-current-defun-function\n              'enh-ruby-add-log-current-method)\n\n  (setq-local font-lock-keywords    enh-ruby-font-lock-keywords)\n  (setq font-lock-defaults          '((enh-ruby-font-lock-keywords) t))\n  (setq indent-tabs-mode            enh-ruby-indent-tabs-mode)\n  (setq imenu-create-index-function #'enh-ruby-imenu-create-index)\n\n  (if enh-ruby-add-encoding-comment-on-save\n    (add-hook 'before-save-hook #'enh-ruby-mode-set-encoding nil t))\n\n  (add-hook 'change-major-mode-hook #'erm-major-mode-changed     nil t)\n  (add-hook 'kill-buffer-hook       #'erm-buffer-killed          nil t)\n\n  (abbrev-mode)\n  (erm-reset-buffer))\n\n;;; Faces:\n\n(require 'color nil t)\n\n(defun erm-darken-color (name)\n  \"Return color NAME with foreground 20% darker.\"\n  (color-darken-name (face-attribute name :foreground) 20))\n\n(defun erm-define-faces ()\n  \"Define faces for ‘enh-ruby-mode’.\"\n\n (defface enh-ruby-string-delimiter-face\n   `((t :foreground ,(erm-darken-color font-lock-string-face)))\n   \"Face used to highlight string delimiters like quotes and %Q.\"\n   :group 'enh-ruby)\n\n (defface enh-ruby-heredoc-delimiter-face\n   `((t :foreground ,(erm-darken-color font-lock-string-face)))\n   \"Face used to highlight string heredoc anchor strings like <<END and END\"\n   :group 'enh-ruby)\n\n (defface enh-ruby-regexp-delimiter-face\n   `((t :foreground ,(erm-darken-color font-lock-string-face)))\n   \"Face used to highlight regexp delimiters like / and %r.\"\n   :group 'enh-ruby)\n\n (defface enh-ruby-regexp-face\n   `((t :foreground ,(face-attribute font-lock-string-face :foreground)))\n   \"Face used to highlight the inside of regular expressions\"\n   :group 'enh-ruby)\n\n (defface enh-ruby-op-face\n   `((t :foreground ,(erm-darken-color font-lock-keyword-face)))\n   \"Face used to highlight operators like + and ||\"\n   :group 'enh-ruby)\n\n (defface erm-syn-errline\n   '((t (:box (:line-width 1 :color \"red\"))))\n   \"Face used for marking error lines.\"\n   :group 'enh-ruby)\n\n (defface erm-syn-warnline\n   '((t (:box (:line-width 1 :color \"orange\"))))\n   \"Face used for marking warning lines.\"\n   :group 'enh-ruby))\n\n(add-hook 'enh-ruby-mode-hook #'erm-define-faces)\n\n;;; Support Functions:\n\n(defun enh-ruby-mode-set-encoding ()\n  \"Check encoding on save and create a magic comment if non-standard encoding.\"\n  (save-excursion\n    (widen)\n    (goto-char (point-min))\n    (when (re-search-forward \"[^[:ascii:]]\" nil t)\n      (goto-char (point-min))\n      (let ((coding-system\n             (or coding-system-for-write\n                 buffer-file-coding-system)))\n        (if coding-system\n            (setq coding-system\n                  (or (coding-system-get coding-system 'mime-charset)\n                      (coding-system-change-eol-conversion coding-system nil))))\n        (setq coding-system\n              (if coding-system\n                  (symbol-name\n                   (or (and enh-ruby-use-encoding-map\n                            (cdr (assq coding-system enh-ruby-encoding-map)))\n                       coding-system))\n                \"ascii-8bit\"))\n        (if (looking-at \"^#!\") (beginning-of-line 2))\n        (cond ((looking-at \"\\\\s *#.*-\\*-\\\\s *\\\\(en\\\\)?coding\\\\s *:\\\\s *\\\\([-a-z0-9_]*\\\\)\\\\s *\\\\(;\\\\|-\\*-\\\\)\")\n               (unless (string= (match-string 2) coding-system)\n                 (goto-char (match-beginning 2))\n                 (delete-region (point) (match-end 2))\n                 (and (looking-at \"-\\*-\")\n                      (let ((n (skip-chars-backward \" \")))\n                        (cond ((= n 0) (insert \"  \") (backward-char))\n                              ((= n -1) (insert \" \"))\n                              ((forward-char)))))\n                 (insert coding-system)))\n              ((looking-at \"\\\\s *#.*coding\\\\s *[:=]\"))\n              ((equal \"utf-8\" coding-system) 'do-nothing) ; hack? should check version?\n              (t (insert \"# -*- coding: \" coding-system \" -*-\\n\")))))))\n\n(defvar erm-ruby-process nil \"The current erm process where Emacs is interacting with.\")\n(defvar erm-response     nil \"Private variable.\")\n(defvar erm-parsing-p    nil \"Parsing: t, nil, \\\\='a (all?), \\\\='p (partial?).\")\n\n(defun erm-ruby-get-process ()\n  \"Return (or create) the current ruby parser process.\"\n  (when (and erm-ruby-process (not (equal (process-status erm-ruby-process) 'run)))\n    (erm-reset)\n    (throw 'interrupted t))\n  (unless erm-ruby-process\n    (let ((process-connection-type nil))\n      (setq erm-ruby-process\n            (start-process \"erm-ruby-process\"\n                           nil\n                           enh-ruby-program (concat (erm-source-dir)\n                                                    \"ruby/erm.rb\")))\n      (set-process-coding-system erm-ruby-process 'utf-8 'utf-8)\n      (set-process-filter erm-ruby-process #'erm-filter)\n      (set-process-query-on-exit-flag erm-ruby-process nil)\n      (process-send-string\n       erm-ruby-process\n       (concat \"x0:\"\n               (mapconcat #'identity (default-value 'enh-ruby-extra-keywords) \" \")\n               \":\"\n               erm-process-delimiter))))\n\n  erm-ruby-process)\n\n(defvar erm-no-parse-needed-p nil \"Private variable.\")\n(defvar erm-source-dir        nil \"Private variable.\")\n\n(defun erm-source-dir ()\n  \"Return the directory for enh-ruby-mode.el.\"\n  (or erm-source-dir\n    (setq erm-source-dir (file-name-directory (find-lisp-object-file-name\n                                               'erm-source-dir\n                                               (symbol-function 'erm-source-dir))))))\n\n\n(defvar erm-next-buff-num nil \"Private variable.\")\n(defvar erm-parse-buff nil \"Private variable.\")\n(defvar erm-reparse-list nil \"Private variable.\")\n(defvar erm-syntax-check-list nil \"Private variable.\")\n\n(defun erm-reset-syntax-buffers (list)\n  (let ((buffer (car list)))\n    (when buffer\n      (when (buffer-live-p buffer)\n        (with-current-buffer buffer (setq need-syntax-check-p nil)))\n      (erm-reset-syntax-buffers (cdr list)))))\n\n(defun erm-ruby-program-version ()\n  \"Display the current version of ERM-RUBY-PROGRAM\"\n  (interactive)\n  (let* ((command (format \"%s -v\" enh-ruby-program))\n         (version (shell-command-to-string command))\n         (version (cl-second (split-string version))))\n   (message \"erm-ruby-program -v = %s\" version)))\n\n(defun erm-reset ()\n  \"Reset all ‘enh-ruby-mode’ buffers and restart the ruby parser.\"\n  (interactive)\n  (erm-reset-syntax-buffers erm-syntax-check-list)\n  (setq erm-reparse-list nil\n        erm-syntax-check-list nil\n        erm-parsing-p nil\n        erm-parse-buff nil\n        erm-next-buff-num 1)\n  (when erm-ruby-process\n    (delete-process erm-ruby-process)\n    (setq erm-ruby-process nil))\n\n  (dolist (buf (buffer-list))\n    (with-current-buffer buf\n      (when (eq 'enh-ruby-mode major-mode)\n        (erm-reset-buffer)))))\n\n(defun erm-major-mode-changed ()\n  (remove-hook 'kill-buffer-hook #'erm-buffer-killed t)\n  (erm-buffer-killed))\n\n(defun erm-proc-string (prefix)\n  (concat prefix (number-to-string erm-buff-num) \":\" erm-process-delimiter))\n\n(defun erm-buffer-killed ()\n  (remove-hook 'kill-buffer-hook #'erm-buffer-killed t)\n  (catch 'interrupted\n   (process-send-string (erm-ruby-get-process) (erm-proc-string \"k\"))))\n\n(defun erm-reset-buffer ()\n  (setq erm-buff-num erm-next-buff-num)\n  (setq erm-next-buff-num (1+ erm-buff-num))\n  (add-hook 'after-change-functions #'erm-req-parse nil t)\n  (unless\n      (enh-ruby-local-enable-extra-keywords)\n    (enh-ruby-fontify-buffer)))\n\n(defun enh-ruby-local-enable-extra-keywords ()\n  \"If the variable `ruby-extra-keywords' is buffer local then\nenable the keywords for current buffer.\"\n  (when (local-variable-p 'enh-ruby-extra-keywords)\n      (process-send-string (erm-ruby-get-process)\n                           (concat \"x\"\n                                   (number-to-string erm-buff-num) \":\"\n                                   (mapconcat #'identity enh-ruby-extra-keywords \" \")\n                                   \":\" erm-process-delimiter))\n      (enh-ruby-fontify-buffer)\n      t))\n\n(defun enh-ruby-electric-brace (arg)\n  (interactive \"P\")\n  (insert-char last-command-event 1)\n  (enh-ruby-indent-line)\n  (delete-char -1)\n  (self-insert-command (prefix-numeric-value arg)))\n\n(defun enh-ruby-brace-to-do-end (orig end)\n  (let (beg-marker end-marker)\n    (goto-char end)\n    (when (eq (char-before) ?\\})\n      (delete-char -1)\n      (when (save-excursion\n              (skip-chars-backward \" \\t\")\n              (not (bolp)))\n        (insert \"\\n\"))\n      (insert \"end\")\n      (setq end-marker (point-marker))\n      (when (and (not (eobp)) (eq (char-syntax (char-after)) ?w))\n        (insert \" \"))\n      (goto-char orig)\n      (delete-char 1)\n      (when (eq (char-syntax (char-before)) ?w)\n        (insert \" \"))\n      (insert \"do\")\n      (setq beg-marker (point-marker))\n      (when (looking-at \"\\\\(\\\\s \\\\)*|\")\n        (unless (match-beginning 1)\n          (insert \" \"))\n        (goto-char (1+ (match-end 0)))\n        (search-forward \"|\"))\n      (unless (looking-at \"\\\\s *$\")\n        (insert \"\\n\"))\n      (indent-region beg-marker end-marker)\n      (goto-char beg-marker))))\n\n(defun enh-ruby-do-end-to-brace (orig end)\n  (let (beg-marker end-marker beg-pos end-pos)\n    (goto-char (- end 3))\n    (when (looking-at enh-ruby-block-end-re)\n      (delete-char 3)\n      (setq end-marker (point-marker))\n      (insert \"}\")\n      (goto-char orig)\n      (delete-char 2)\n      ;; Maybe this should be customizable, let's see if anyone asks.\n      (insert \"{ \")\n      (setq beg-marker (point-marker))\n      (when (looking-at \"\\\\s +|\")\n        (delete-char (- (match-end 0) (match-beginning 0) 1))\n        (forward-char)\n        (re-search-forward \"|\" (line-end-position) t))\n      (save-excursion\n        (skip-chars-forward \" \\t\\n\\r\")\n        (setq beg-pos (point))\n        (goto-char end-marker)\n        (skip-chars-backward \" \\t\\n\\r\")\n        (setq end-pos (point)))\n      (when (or\n             (< end-pos beg-pos)\n             (and (= (line-number-at-pos beg-pos) (line-number-at-pos end-pos))\n                  (< (+ (current-column) (- end-pos beg-pos) 2) fill-column)))\n        (just-one-space -1)\n        (goto-char end-marker)\n        (just-one-space -1))\n      (goto-char beg-marker))))\n\n(defun enh-ruby-toggle-block ()\n  \"Toggle block type from do-end to braces or back.\nThe block must begin on the current line or above it and end after the point.\nIf the result is do-end block, it will always be multiline.\"\n  (interactive)\n  (let* ((pos (point))\n         (block-start (save-excursion\n                        (end-of-line)\n                        (while (and (not (bobp))\n                                    (not (enh-ruby-point-block-p)))\n                          (backward-char))\n                        (point)))\n         (block-end (save-excursion\n                      (goto-char block-start)\n                      (enh-ruby-forward-sexp)\n                      (point))))\n    (if (< pos block-end)\n        (if (eq (char-after block-start) ?{)\n            (enh-ruby-brace-to-do-end block-start block-end)\n          (enh-ruby-do-end-to-brace block-start block-end)))))\n\n(defun enh-ruby-imenu-create-index-in-block (_prefix beg end)\n  (let* ((index-alist '())\n         (pos beg)\n         (prop (get-text-property pos 'indent)))\n    (setq end (or end (point-max)))\n    (while (and pos (< pos end))\n      (goto-char pos)\n      (when (and (eq prop 'b) (looking-at enh-ruby-defun-and-name-re))\n        (push (cons (concat (match-string 1) \" \" (match-string 2)) pos) index-alist))\n\n      (setq prop (and (setq pos (enh-ruby-next-indent-change pos))\n                      (get-text-property pos 'indent))))\n\n    index-alist))\n\n(defun enh-ruby-imenu-create-index ()\n  (nreverse (enh-ruby-imenu-create-index-in-block nil (point-min) nil)))\n\n(defun enh-ruby-add-log-current-method ()\n  \"Return current method string.\"\n  (condition-case nil\n      (save-excursion\n        (enh-ruby-beginning-of-defun 1)\n        (when (looking-at enh-ruby-defun-and-name-re)\n          (let ((def-or-mod (match-string-no-properties 1))\n                (def-name   (match-string-no-properties 2)))\n            (if (string= \"def\" def-or-mod)\n                (progn\n                  (enh-ruby-up-sexp)\n                  (when (looking-at enh-ruby-defun-and-name-re)\n                    (let ((_mod-or-class (match-string-no-properties 1))\n                          (mod-name   (match-string-no-properties 2)))\n                      (let* ((meth-name-re (concat\n                                            (regexp-opt (list \"self\" mod-name)\n                                                        'words)\n                                            \"\\\\.\\\\(.+\\\\)\"))\n                             (cls-meth (and (string-match meth-name-re def-name)\n                                            (match-string 2 def-name)))\n                             (name (or cls-meth def-name))\n                             (sep (if cls-meth \".\" \"#\")))\n                        (concat mod-name sep name)))))\n              nil))))))\n\n;; Stolen shamelessly from James Clark's nxml-mode.\n(defmacro erm-with-unmodifying-text-property-changes (&rest body)\n  \"Evaluate BODY without modifying the buffer's text properties.\nAny text properties changes happen as usual but the changes are\nnot treated as modifications to the buffer.\"\n  (let ((modified (make-symbol \"modified\")))\n    `(let ((,modified (buffer-modified-p))\n           (inhibit-read-only t)\n           (inhibit-modification-hooks t)\n           (buffer-undo-list t)\n           (deactivate-mark nil)\n           ;; Apparently these avoid file locking problems.\n           (buffer-file-name nil)\n           (buffer-file-truename nil))\n       (unwind-protect\n           (progn ,@body)\n         (unless ,modified\n           (restore-buffer-modified-p nil))))))\n\n(defun enh-ruby-fontify-buffer ()\n  \"Fontify the current buffer. Useful if faces are out of sync.\"\n  (interactive)\n  (if (and erm-parsing-p\n           (not (eq erm-parse-buff (current-buffer))))\n      (erm-reparse-diff-buf)\n    (setq erm-full-parse-p t)\n    (condition-case nil\n        (erm-req-parse nil nil nil)\n      (error nil))))\n\n(defun erm-reparse-diff-buf ()\n  (setq erm-reparse-list (cons (current-buffer) erm-reparse-list)))\n\n(defun erm-req-parse (min max len)\n  (when (and enh-ruby-check-syntax (not need-syntax-check-p))\n    (setq need-syntax-check-p t)\n    (setq erm-syntax-check-list (cons (current-buffer) erm-syntax-check-list)))\n  (let ((pc (if erm-parsing-p\n                (if (eq erm-parse-buff (current-buffer))\n                    (setq erm-parsing-p 'a)\n                  'dbuf)\n              (setq erm-response \"\")\n              (setq erm-parsing-p t)\n              (if (not erm-full-parse-p)\n                  (if erm-no-parse-needed-p\n                      (progn (setq erm-parsing-p nil) 'a)\n                    'p)\n                (setq min (point-min)\n                      max (point-max)\n                      len 0\n                      erm-full-parse-p nil)\n                'r)))\n        interrupted-p)\n    (setq interrupted-p\n          (catch 'interrupted\n            (if (eq pc 'dbuf)\n                (erm-reparse-diff-buf)\n              (setq erm-parse-buff (current-buffer))\n              (process-send-string (erm-ruby-get-process)\n                                   (format \"%s%d:%d:%d:%d:%d:\"\n                                           pc\n                                           erm-buff-num\n                                           (point-min)\n                                           (point-max)\n                                           min\n                                           len))\n              (process-send-region erm-ruby-process min max)\n              (process-send-string erm-ruby-process erm-process-delimiter))\n            nil))\n    (when interrupted-p\n      (setq erm-full-parse-p t))))\n\n(defun erm-wait-for-parse ()\n  (while erm-parsing-p\n    (accept-process-output (erm-ruby-get-process) 0.5)))\n\n(defun erm-filter (_proc response)\n  (setq erm-response (concat erm-response response))\n  (when (and (> (length erm-response) 5)\n             (string= erm-process-delimiter (substring erm-response -5 nil)))\n    (setq response (substring erm-response 0 -5))\n    (setq erm-response \"\")\n    (if (buffer-live-p erm-parse-buff)\n        (with-current-buffer erm-parse-buff\n          (erm-with-unmodifying-text-property-changes\n           (erm-parse response)))\n      (erm-reset))))\n\n(defun erm-ready ()\n  (if erm-full-parse-p\n      (enh-ruby-fontify-buffer)\n    (setq erm-parsing-p t)\n    (process-send-string (erm-ruby-get-process) (erm-proc-string \"g\"))))\n\n(defun extra-col-% ()\n  \"Return extra column adjustments in case we are ‘looking-at’ a % construct.\"\n  (or (and (looking-at \"%\\\\([^[:alnum:]]\\\\|[QqWwIixrs].\\\\)\")\n           (1- (length (match-string-no-properties 0))))\n      0))\n\n(defun enh-ruby-continue-p (prop)\n  \"Return whether PROP is a continue property.\"\n  (eq 'c prop))\n\n(defun enh-ruby-block-p (prop)\n  \"Return whether PROP is a block property.\"\n  (eq 'd prop))\n\n(defun enh-ruby-point-continue-p (point)\n  \"Return whether property at POINT is a continue property.\"\n  (enh-ruby-continue-p (get-text-property point 'indent)))\n\n(defun enh-ruby-point-block-p (&optional point)\n  \"Return whether property at POINT is a block property.\"\n  (or point (setq point (point)))\n  (enh-ruby-block-p (get-text-property point 'indent)))\n\n(defun enh-ruby-calculate-indent (&optional start-point)\n  \"Calculate the indentation of the previous line and its level at START-POINT.\"\n  (save-excursion\n    (when start-point (goto-char start-point))\n    (if (bobp)\n        0\n      (forward-line 0)\n      (skip-syntax-forward \" \" (line-end-position))\n      (let ((pos (line-beginning-position))\n            (prop (get-text-property (point) 'indent))\n            (face (get-text-property (point) 'font-lock-face)))\n        (cond\n         ((or (eq 'e prop) (eq 's prop))\n          (when (eq 's prop) (forward-char))\n          (enh-ruby-backward-sexp)\n          (let ((bprop (get-text-property (point) 'indent)))\n            (cond ((eq 'd bprop)\n                   (setq pos (point))\n                   (enh-ruby-skip-non-indentable)\n                   (let ((indent (enh-ruby-calculate-indent-1 pos (line-beginning-position)))\n                         (chained-stmt-p (save-excursion\n                                           (forward-line 0)\n                                           (enh-ruby-point-continue-p (point)))))\n                     (+ indent\n                        (if chained-stmt-p enh-ruby-hanging-indent-level 0))))\n                  ((and (not enh-ruby-deep-indent-construct)\n                        (eq 'b bprop))\n                   (current-indentation))\n                  (t\n                   (current-column)))))\n         ((eq 'r prop)                  ; TODO: make these consistent file-wide\n          (let (opening-col opening-is-last-thing-on-line)\n            (save-excursion\n              (enh-ruby-backward-sexp)\n              (setq opening-col (+ (current-column)\n                                   (extra-col-%)))\n              (forward-char 1)\n              (skip-syntax-forward \" \" (line-end-position))\n              (setq opening-is-last-thing-on-line (eolp)))\n            (if (and enh-ruby-deep-indent-paren\n                     (not enh-ruby-bounce-deep-indent)\n                     (not opening-is-last-thing-on-line))\n                opening-col             ; deep + !bounce + !hanging = match open\n              (forward-line -1)\n              (enh-ruby-skip-non-indentable)\n              (let* ((opening-char (save-excursion\n                                     (enh-ruby-backward-sexp)\n                                     (char-after)))\n                     (proposed-col (enh-ruby-calculate-indent-1 pos\n                                                                (line-beginning-position)))\n                     (chained-stmt-p (save-excursion (enh-ruby-backward-sexp)\n                                                     (forward-line 0)\n                                                     (enh-ruby-point-continue-p (point))))\n                     (offset (if (char-equal opening-char ?{)\n                                 enh-ruby-hanging-brace-indent-level\n                               enh-ruby-hanging-paren-indent-level)))\n                (cond ((and chained-stmt-p\n                            (not enh-ruby-bounce-deep-indent))\n                       (- proposed-col offset))\n                      ((< proposed-col opening-col)\n                       (- proposed-col offset))\n                      (t opening-col))))))\n\n         ((or (memq face '(font-lock-string-face enh-ruby-heredoc-delimiter-face))\n              (and (eq 'font-lock-variable-name-face face)\n                   (looking-at \"#\")))\n          (when enh-ruby-preserve-indent-in-heredocs\n            (forward-line -1)\n            (back-to-indentation))\n          (current-column))\n\n         (t\n          (forward-line -1)\n\n          (enh-ruby-skip-non-indentable)\n          (enh-ruby-calculate-indent-1 pos (line-beginning-position))))))))\n\n(defun erm-looking-at-not-indentable ()\n  (skip-syntax-forward \" \" (line-end-position))\n  (let ((face (get-text-property (point) 'font-lock-face)))\n    (or (= (point) (line-end-position))\n        (memq face '(font-lock-string-face font-lock-comment-face enh-ruby-heredoc-delimiter-face))\n        (and (eq 'font-lock-variable-name-face face)\n             (looking-at \"#\"))\n        (and (memq face '(enh-ruby-regexp-delimiter-face enh-ruby-string-delimiter-face))\n             (> (point) (point-min))\n             (eq (get-text-property (1- (point)) 'font-lock-face)\n                 'font-lock-string-face)))))\n\n(defun enh-ruby-skip-non-indentable ()\n  (forward-line 0)\n  (while (and (> (point) (point-min))\n              (erm-looking-at-not-indentable))\n    (skip-chars-backward \" \\n\\t\\r\\v\\f\")\n    (forward-line 0)))\n\n(defvar enh-ruby-last-bounce-line nil\n  \"The last line that `erm-bounce-deep-indent-paren` was run against.\")\n\n(defvar enh-ruby-last-bounce-deep nil\n  \"The last result from `erm-bounce-deep-indent-paren`.\")\n\n(defun enh-ruby-calculate-indent-1 (limit pos)\n  (goto-char pos)\n\n  (let* ((start-pos pos)\n         (start-prop (get-text-property pos 'indent))\n         (prop start-prop)\n         (indent (- (current-indentation)\n                    (if (eq 'c prop) enh-ruby-hanging-indent-level 0)))\n         (nbc 0)\n         (npc 0)\n         col max bc pc)\n\n    (setq enh-ruby-last-bounce-deep\n          (and (eq enh-ruby-last-bounce-line (line-number-at-pos))\n               (not enh-ruby-last-bounce-deep)))\n    (setq enh-ruby-last-bounce-line (line-number-at-pos))\n\n    (while (< pos limit)\n      (unless prop\n        (setq pos (next-single-property-change pos 'indent (current-buffer) limit))\n        (when (< pos limit)\n          (setq prop (get-text-property pos 'indent))))\n      (setq col (- pos start-pos -1))\n\n      (cond\n       ;; 'l - [, (, {, %w/%i open  or | goalpost open\n       ;; 'r - ], ), }, %w/%i close or | goalpost close\n       ;; 'b - begin, def, case, if\n       ;; 'd - do, {, embexpr (interpolation) start\n       ;; 'e - end, embexpr (interpolation) end, close block }\n       ;; 's - statement start on BACKDENT_KW else/when/rescue etc\n       ;; 'c - continue - period followed by return (or other way around?)\n       ((memq prop '(l))\n        (let ((shallow-indent\n               (if (char-equal (char-after pos) ?{)\n                   (+ enh-ruby-hanging-brace-indent-level indent)\n                 (+ enh-ruby-hanging-paren-indent-level indent)))\n              (deep-indent\n               (cond ((char-equal (char-after pos) ?{)\n                      (+ enh-ruby-hanging-brace-deep-indent-level col))\n                     ((char-equal (char-after pos) ?%)\n                      (+ enh-ruby-hanging-brace-deep-indent-level\n                         col\n                         (save-excursion\n                           (goto-char pos)\n                           (extra-col-%))))\n                     (t (+ enh-ruby-hanging-paren-deep-indent-level col))))\n              (at-eol (save-excursion\n                        (goto-char (1+ pos))\n                        (skip-syntax-forward \" \" (line-end-position))\n                        (eolp))))\n          (if enh-ruby-bounce-deep-indent\n              (setq pc (cons (if enh-ruby-last-bounce-deep\n                                 shallow-indent\n                               deep-indent)\n                             pc))\n            (setq pc (cons (if (and (not at-eol) enh-ruby-deep-indent-paren)\n                               deep-indent\n                             (let ((chained-stmt-p (enh-ruby-continue-p start-prop)))\n                               (+ shallow-indent (if chained-stmt-p enh-ruby-hanging-paren-indent-level 0))))\n                           pc)))))\n\n       ((eq prop 'r)\n        (if pc (setq pc (cdr pc)) (setq npc col)))\n\n       ((memq prop '(b d s))\n        (and (not enh-ruby-deep-indent-construct)\n             (eq prop 'b)\n             (setq col\n                   (- col (- (save-excursion\n                               (goto-char pos)\n                               (current-column))\n                             (current-indentation)))))\n        (setq bc (cons col bc)))\n\n       ((eq prop 'e)\n        (if bc\n            (setq bc (cdr bc))\n          (setq nbc col))))\n\n      (when (< (setq pos (1+ pos)) limit)\n        (setq prop (get-text-property pos 'indent))))\n\n    ;;(prin1 (list indent nbc bc npc pc))\n    (setq pc (or (car pc) 0))\n    (setq bc (or (car bc) 0))\n    (setq max (max pc bc nbc npc))\n\n    (+\n     (if (eq 'c (get-text-property limit 'indent)) enh-ruby-hanging-indent-level 0)\n     (cond\n      ((= max 0)\n       (if (not (memq (get-text-property start-pos 'font-lock-face)\n                      '(enh-ruby-heredoc-delimiter-face font-lock-string-face)))\n           indent\n         (goto-char (or (enh-ruby-string-start-pos start-pos) limit))\n         (current-indentation)))\n\n      ((= max pc) (if (eq 'c (get-text-property limit 'indent))\n                      (- pc enh-ruby-hanging-indent-level)\n                    pc))\n\n      ((= max bc)\n       (if (eq 'd (get-text-property (+ start-pos bc -1) 'indent))\n           (let ((chained-stmt-p (enh-ruby-continue-p start-prop)))\n             (+ (enh-ruby-calculate-indent-1 (+ start-pos bc -1) start-pos)\n                (* (if chained-stmt-p 2 1) enh-ruby-indent-level)))\n         (+ bc enh-ruby-indent-level -1)))\n\n      ((= max npc)\n       (goto-char (+ start-pos npc))\n       (enh-ruby-backward-sexp)\n       (enh-ruby-calculate-indent-1 (point) (line-beginning-position)))\n\n      ((= max nbc)\n       (goto-char (+ start-pos nbc -1))\n       (enh-ruby-backward-sexp)\n       (enh-ruby-calculate-indent-1 (point) (line-beginning-position)))\n\n      (t 0)))))\n\n(defun enh-ruby-string-start-pos (pos)\n  (when (< 0 (or (setq pos (previous-single-property-change pos 'font-lock-face)) 0))\n    (previous-single-property-change pos 'font-lock-face)))\n\n(defun enh-ruby-show-errors-at (pos face)\n  (let ((overlays (overlays-at pos))\n        overlay\n        messages)\n\n    ;; TODO:\n    ;; (-map (lambda (o) (overlay-get o 'help-echo))\n    ;;       (-filter (lambda (o) (and (overlay-get o 'erm-syn-overlay)\n    ;;                                 (eq (overlay-get o 'font-lock-face) face)))))\n\n    (while overlays\n      (setq overlay (car overlays))\n      (when (and (overlay-get overlay 'erm-syn-overlay)\n                 (eq (overlay-get overlay 'font-lock-face) face))\n        (setq messages (cons (overlay-get overlay 'help-echo) messages)))\n      (setq overlays (cdr overlays)))\n\n    (message \"%s\" (mapconcat #'identity messages \"\\n\"))\n    messages))\n\n(defun enh-ruby-find-error (&optional warnings)\n  \"Search back, then forward for a syntax error/warning. Display\ncontents in mini-buffer. Optional WARNINGS will highlight\nwarnings instead of errors. (I think).\"\n  (interactive \"^P\")\n  (let (messages\n        (face (if warnings 'erm-syn-warnline 'erm-syn-errline))\n        (pos (point)))\n    (unless (eq last-command #'enh-ruby-find-error)\n      (while (and (not messages) (> pos (point-min)))\n        (setq messages (enh-ruby-show-errors-at (setq pos (previous-overlay-change pos)) face))))\n\n    (unless messages\n      (while (and (not messages) (< pos (point-max)))\n        (setq messages (enh-ruby-show-errors-at (setq pos (next-overlay-change pos)) face))))\n\n    (if messages\n        (goto-char pos)\n      (unless warnings\n        (enh-ruby-find-error t)))))\n\n(defun enh-ruby-find-file (filename)\n  \"Search for and edit FILENAME. Searching is done with `gem\nwhich` but works for standard lib as well as gems.\"\n  (interactive \"sgem which \")\n  (let* ((command (concat \"gem which \" filename))\n         (output  (shell-command-to-string command))\n         (path    (string-trim-right output)))\n    (if (file-exists-p path)\n        (find-file path)\n      (message \"%S found nothing\" command))))\n\n(defun enh-ruby-up-sexp (&optional arg)\n  \"Move up one balanced expression (sexp).\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (while (>= (setq arg (1- arg)) 0)\n    (let* ((count 1)\n           prop)\n      (goto-char\n       (save-excursion\n         (while (and (not (= (point) (point-min)))\n                     (< 0 count))\n           (goto-char (enh-ruby-previous-indent-change (point)))\n           (setq prop (get-text-property (point) 'indent))\n           (setq count (cond\n                        ((or (eq prop 'l) (eq prop 'b) (eq prop 'd)) (1- count))\n                        ((or (eq prop 'r) (eq prop 'e)) (1+ count))\n                        (t count))))\n         (point))))))\n\n(defun enh-ruby-beginning-of-defun (&optional arg)\n  \"Move backward across expression (sexp) looking for a definition beginning.\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (let (prop)\n    (goto-char\n     (save-excursion\n       (while (>= (setq arg (1- arg)) 0)\n         (while (and\n                 (> (point) (point-min))\n                 (progn\n                  (enh-ruby-backward-sexp 1)\n                  (setq prop (get-text-property (point) 'indent))\n                  (not (and (eq prop 'b) (looking-at enh-ruby-defun-beg-re)))))))\n       (point)))))\n\n(defun enh-ruby-mark-defun ()\n  \"Put mark at end of this Ruby definition, point at beginning.\"\n  (interactive)\n  (push-mark (point))\n  (enh-ruby-beginning-of-defun 1)\n  (enh-ruby-forward-sexp 1)\n  (forward-line 1)\n  (push-mark (point) nil t)\n  (forward-line -1)\n  (end-of-line)\n  (enh-ruby-backward-sexp 1)\n  (forward-line 0))\n\n(defun enh-ruby-indent-exp (&optional _shutup-p)\n  \"Indent each line in the balanced expression following point syntactically.\"\n  (interactive \"*P\")\n  (erm-wait-for-parse)\n  (let ((end-pos (save-excursion (enh-ruby-forward-sexp 1) (point))))\n    (indent-region (point) end-pos)))\n\n(set-advertised-calling-convention 'enh-ruby-indent-exp '() \"2022-04-26\")\n\n(defun enh-ruby-beginning-of-block (&optional arg)\n  \"Move backward across one expression (sexp) looking for a block beginning.\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (let (prop\n        pos)\n    (goto-char\n     (save-excursion\n       (while (>= (setq arg (1- arg)) 0)\n         (while (progn\n                  (enh-ruby-backward-sexp 1)\n                  (setq pos (point))\n                  (setq prop (get-text-property pos 'indent))\n                  (and\n                   (> pos (point-min))\n                   (not (or (eq prop 'b) (eq prop 'd)))))))\n       (point)))))\n\n(defun enh-ruby-end-of-defun (&optional arg)\n  \"Move forwards across one expression (sexp) looking for a definition end.\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (let (prop)\n    (while (>= (setq arg (1- arg)) 0)\n         (while (and\n                 (< (point) (point-max))\n                 (progn\n                   (enh-ruby-forward-sexp 1)\n                   (setq prop (get-text-property (- (point) 3) 'indent))\n                   (not (and (eq prop 'e)\n                             (save-excursion\n                               (enh-ruby-backward-sexp 1)\n                               (looking-at enh-ruby-defun-beg-re))))))))\n    (point)))\n\n(defun enh-ruby-end-of-block (&optional arg)\n  \"Move forwards across one balanced expression (sexp) looking for a block end.\nWith ARG, do it that many times.\"\n  ;; this is totally broken for {} blocks! see the -3 below\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (let (prop\n        pos)\n    (goto-char\n     (save-excursion\n       (while (>= (setq arg (1- arg)) 0)\n         (while (progn\n                  (enh-ruby-forward-sexp 1)\n                  (setq pos (point))\n                  (setq prop (get-text-property (- pos 3) 'indent))\n                  (and (< pos (point-max)) (not (or (eq prop 'e) ; closers\n                                                    (eq prop 'r))\n                                                )))))\n       (point)))))\n\n(defun enh-ruby-backward-sexp (&optional arg)\n  \"Move backward across one balanced expression (sexp).\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n\n  (unless arg (setq arg 1))\n  (while (>= (setq arg (1- arg)) 0)\n    (let* ((pos (point))\n           (prop (get-text-property pos 'indent))\n           (count 0))\n\n      (unless (memq prop '(r e))\n        (setq prop (and (setq pos (enh-ruby-previous-indent-change pos))\n                        (goto-char pos) ;; TODO: remove?\n                        (get-text-property pos 'indent))))\n\n      (while (< 0 (setq count\n                        (cond\n                         ((memq prop '(l b d)) (1- count))\n                         ((memq prop '(r e))   (1+ count))\n                         ((eq prop 'c)         count)\n                         ((eq prop 's)         (if (= 0 count) 1 count))\n                         (t                    0))))\n        (goto-char pos)\n        (setq prop (and (setq pos (enh-ruby-previous-indent-change pos))\n                        (get-text-property pos 'indent))))\n\n      (goto-char (if prop pos (point-min))))))\n\n;; 'l - [, (, {, %w/%i open  or | goalpost open\n;; 'r - ], ), }, %w/%i close or | goalpost close\n;; 'b - begin, def, case, if\n;; 'd - do, {, embexpr (interpolation) start\n;; 'e - end, embexpr (interpolation) end, close block }\n;; 's - statement start on BACKDENT_KW else/when/rescue etc\n;; 'c - continue - period followed by return (or other way around?)\n\n;; backwards: l b d s?\n;; forwards r e\n\n;; C-M-a           enh-ruby-beginning-of-defun\n;; C-M-p           enh-ruby-beginning-of-block\n;; C-M-e           enh-ruby-end-of-defun\n;; C-M-n           enh-ruby-end-of-block\n;; C-M-q           enh-ruby-indent-exp\n;; C-M-h           enh-ruby-mark-defun\n;; C-M-u           enh-ruby-up-sexp\n\n(defun enh-ruby-forward-sexp (&optional arg)\n  \"Move backward across one balanced expression (sexp).\nWith ARG, do it that many times.\"\n  (interactive \"^p\")\n  (unless arg (setq arg 1))\n  (let ((i (or arg 1)))\n    (cond\n     ((< i 0)\n      (enh-ruby-backward-sexp (- i)))\n     (t\n      (skip-syntax-forward \" \")\n      (while (> i 0)\n        (let* ((pos (point))\n               (prop (get-text-property pos 'indent))\n               (count 0))\n\n          (unless (memq prop '(l b d))\n            (setq prop (and (setq pos (enh-ruby-next-indent-change pos))\n                            (get-text-property pos 'indent))))\n\n          (while (< 0 (setq count\n                            (cond\n                             ((memq prop '(l b d)) (1+ count))\n                             ((memq prop '(r e))   (1- count))\n                             ((memq prop '(c))     count)\n                             ((memq prop '(s))     (if (= 0 count) 1 count))\n                             (t                    0))))\n            (goto-char pos)\n            (setq prop (and (setq pos (enh-ruby-next-indent-change pos))\n                            (get-text-property pos 'indent))))\n\n          (goto-char (if prop pos (point-max)))\n\n          (cond ((looking-at \"end\")     ; move past end/}/]/)\n                 (forward-word 1))\n                ((looking-at \"[]})]\")\n                 (forward-char 1))))\n\n        (setq i (1- i)))))))\n\n(defun enh-ruby-insert-end ()\n  (interactive)\n  (let ((text (save-excursion\n                (forward-line 0)\n                (if (looking-at \"^[ \\t]*$\")\n                    \"end\"\n                  (if (looking-at \".*{[^}]*$\")\n                      \"\\n}\"\n                    \"\\nend\")))))\n    (insert text)\n    (enh-ruby-indent-line)))\n\n(defun enh-ruby-previous-indent-change (pos)\n  (and pos\n       (setq pos (1- pos))\n       (>= pos (point-min))\n       (or (and (get-text-property pos 'indent)\n                pos)\n           (and (> pos (point-min))\n                (get-text-property (1- pos) 'indent)\n                (1- pos))\n           (enh-ruby-previous-indent-change (previous-single-property-change pos 'indent))\n           (point-min))))\n\n(defun enh-ruby-next-indent-change (pos)\n  (and pos (setq pos (1+ pos))\n       (<= pos (point-max))\n       (or (and (get-text-property pos 'indent) pos)\n           (and (< pos (point-max))\n                (get-text-property (1+ pos) 'indent)\n                (1+ pos))\n           (next-single-property-change pos 'indent))))\n\n(defun enh-ruby-indent-line (&optional _ignored)\n  \"Correct indentation of the current ruby line.\"\n  (erm-wait-for-parse)\n  (unwind-protect\n      (progn\n        (setq erm-no-parse-needed-p t)\n        (enh-ruby-indent-to (enh-ruby-calculate-indent)))\n    (setq erm-no-parse-needed-p nil)))\n\n(defun enh-ruby-indent-to (indent)\n  \"Indent the current line until INDENT is reached.\"\n  (unless (= (current-indentation) indent)\n    (save-excursion\n      (beginning-of-line)\n      (let ((pos (point))\n            (prop (get-text-property (point) 'indent)))\n        (delete-horizontal-space)\n        (indent-to indent)\n        (if (eq 'c prop) (put-text-property pos (1+ pos) 'indent 'c)))))\n\n  (if (< (current-column) (current-indentation))\n      (back-to-indentation)))\n\n(defun enh-ruby-add-faces (list)\n  (let* ((ipos     (car   list))\n         (buf-size (car   ipos))\n         (istart   (cadr  ipos))\n         (iend     (cl-caddr ipos))\n         (rpos     (cdr   (cadr list))))\n\n    (unless (and (= (buffer-size) buf-size))\n      (throw 'interrupted t))\n\n    (if (or (/= (point-min) istart) (/= (point-max) iend))\n        (setq erm-full-parse-p t)\n\n      (when (> iend 0)\n        (remove-text-properties istart iend '(indent nil))\n\n        (setq ipos (cl-cdddr ipos))\n\n        (while ipos\n          (put-text-property (cadr ipos) (1+ (cadr ipos)) 'indent (car ipos))\n          (setq ipos (cddr ipos)))\n\n        (while rpos\n          (remove-text-properties (car rpos) (cadr rpos) '(font-lock-face nil))\n          (setq rpos (cddr rpos))))\n\n      (while (setq list (cdr list))\n        (let ((face (nth (caar list) enh-ruby-font-names))\n              (pos (cdar list)))\n          (while pos\n            (put-text-property (car pos) (cadr pos) 'font-lock-face face)\n            (setq pos (cddr pos))))))))\n\n(defun erm-syntax-response (response)\n  (save-excursion\n    (dolist (ol (overlays-in (point-min) (point-max)))\n    (when (and (overlayp ol) (overlay-get ol 'erm-syn-overlay))\n        (delete-overlay ol)))\n    (goto-char (point-min))\n    (let ((warn-count 0)\n          (error-count 0)\n          (e-w erm-e-w-status)\n          (last-line 1))\n      (while (string-match \":\\\\([0-9]+\\\\): *\\\\(\\\\(warning\\\\)?[^\\n]+\\\\)\\n\" response)\n        (let (beg end ov\n                  (line-no (string-to-number (match-string 1 response)))\n                  (msg (match-string 2 response))\n                  (face (if (string= \"warning\" (match-string 3 response)) 'erm-syn-warnline 'erm-syn-errline)))\n          (setq response (substring response (match-end 0)))\n          (forward-line (- line-no last-line))\n\n          (when (or (eq face 'erm-syn-errline) (eq enh-ruby-check-syntax 'errors-and-warnings))\n            (if (and (not (eq ?: (string-to-char response)))\n                     (string-match \"\\\\`[^\\n]*\\n\\\\( *\\\\)\\\\^\\n\" response))\n                (progn\n                  (setq beg (point))\n                  (condition-case nil\n                      (forward-char  (length (match-string 1 response)))\n                    (error (goto-char (point-max))))\n                  (setq end (point))\n\n                  (condition-case nil\n                      (progn\n                        (backward-sexp)\n                        (forward-sexp))\n\n                    (error (back-to-indentation)))\n                  (setq beg (if (>= (point) end)\n                                (1- end)\n                              (if (< (point) beg)\n                                  (if (>= beg end) (1- end) beg)\n                                (point)))))\n\n              (move-end-of-line nil)\n              (skip-chars-backward \" \\n\\t\\r\\v\\f\")\n              (while (and (> (point) (point-min))\n                          (eq 'font-lock-comment-face (get-text-property (point) 'font-lock-face)))\n                (backward-char))\n              (skip-chars-backward \" \\n\\t\\r\\v\\f\")\n              (setq end (point))\n              (back-to-indentation)\n              (setq beg (point)))\n\n            (if (eq face 'erm-syn-warnline)\n                (setq warn-count (1+ warn-count))\n              (setq error-count (1+ error-count)))\n\n            (setq ov (make-overlay beg end nil t t))\n            (overlay-put ov 'font-lock-face face)\n            (overlay-put ov 'help-echo      msg)\n            (overlay-put ov 'erm-syn-overlay  t)\n            (overlay-put ov 'priority (if (eq 'erm-syn-warnline face) 99 100)))\n\n          (setq last-line line-no)))\n      (if (eq (+ error-count warn-count) 0)\n          (setq e-w nil)\n        (setq e-w (format \":%d/%d\" error-count warn-count)))\n      (when (not (string= e-w erm-e-w-status))\n        (setq erm-e-w-status e-w)\n        (force-mode-line-update)))))\n\n(defun erm-do-syntax-check ()\n  (unless erm-parsing-p\n    (let ((buffer (car erm-syntax-check-list)))\n      (setq erm-syntax-check-list (cdr erm-syntax-check-list))\n      (if (buffer-live-p buffer)\n          (with-current-buffer buffer\n            (when need-syntax-check-p\n              (setq need-syntax-check-p nil)\n              (setq erm-parsing-p t)\n              (process-send-string (erm-ruby-get-process) (erm-proc-string \"c\"))))\n        (if erm-syntax-check-list\n            (erm-do-syntax-check))))))\n\n(defun erm-parse (response)\n  (let (interrupted-p\n        (cmd (aref response 0))\n        (send-next-p (eq 'a erm-parsing-p)))\n    (setq erm-parsing-p nil)\n    (cond\n     ((eq ?\\( cmd)\n          (setq interrupted-p\n                (condition-case nil\n                    (catch 'interrupted\n                      (if send-next-p\n                          (erm-ready)\n                        (enh-ruby-add-faces (car (read-from-string response))))\n                      nil)\n                  (error t)))\n          (if interrupted-p\n              (setq erm-full-parse-p t)\n            (if erm-full-parse-p\n                (enh-ruby-fontify-buffer)\n              (let ((current (car erm-reparse-list)))\n                (when (and current (buffer-live-p current))\n                    (setq erm-reparse-list (cdr erm-reparse-list))\n                    (with-current-buffer current\n                      (enh-ruby-fontify-buffer))\n                  (erm-do-syntax-check))))))\n\n     ((eq ?c cmd)\n      (unless need-syntax-check-p\n        (erm-syntax-response (substring response 1)))\n      (erm-do-syntax-check))\n\n     (t\n      (setq erm-full-parse-p t)\n      (error \"%s\" (substring response 1))))))\n\n(defun erm--end-p ()\n  \"Is point directly after a block closing \\\"end\\\".\"\n  (let ((end-pos (- (point) 3)))\n    (and (>= end-pos (point-min))\n         (string= \"end\" (buffer-substring end-pos (point)))\n         (eq (get-text-property end-pos 'indent) 'e))))\n\n(defun erm-show-paren-data-function ()\n  ;; First check if we are on opening ('b or 'd). We only care about\n  ;; the word openers \"if\", \"do\" etc (normal show-paren handles \"{\")\n  (if (and (memq (get-text-property (point) 'indent) '(b d))\n           (looking-at \"\\\\w\"))\n      (save-excursion\n        (let ((opener-beg (point))\n              (opener-end (save-excursion (forward-word) (point)))\n              (closer-end (progn (enh-ruby-forward-sexp 1) (point))))\n          (list\n           opener-beg\n           opener-end\n           (save-excursion (skip-syntax-backward \")w\") (point))\n           closer-end\n           (not (erm--end-p)))))\n    ;; Now check if we are at a closer (\"end\")\n    (if (erm--end-p)\n        (let ((end-pos (point)))\n          (save-excursion\n            (enh-ruby-backward-sexp 1)\n            (list\n             (- end-pos 3)\n             end-pos\n             (point)\n             (save-excursion (skip-syntax-forward \"(w\") (point))\n             (or (not (looking-at \"\\\\w\"))\n                 (not (memq (get-text-property (point) 'indent) '(b d)))))))\n      (show-paren--default))))\n\n;;; Debugging / Bug Reporting:\n\n(defun enh-ruby--all-vars-with (pattern)\n  \"Return all defcustom variables that match PATTERN.\nUsed for inserting file-local-variables and sending in bug reports.\"\n  (let (mode-vars)\n    (mapatoms (lambda (symbol)\n                (when (and (string-match-p pattern (symbol-name symbol))\n                           (get symbol 'standard-value))\n                  (add-to-list 'mode-vars symbol))))\n    (sort mode-vars\n          (lambda (a b) (string< (symbol-name a) (symbol-name b))))))\n\n(defun enh-ruby--variable-standard-p (sym)\n  (and (equal (custom-variable-state sym (symbol-value sym))\n              'standard)\n       (equal (symbol-value sym)\n              (default-value sym))))\n\n(defun enh-ruby--changed-vars-with (pattern)\n  \"Return all changed defcustom variables that match PATTERN.\nUsed for inserting file-local-variables and sending in bug reports.\"\n  (seq-remove\n   #'enh-ruby--variable-standard-p\n   (enh-ruby--all-vars-with pattern)))\n\n(defun enh-ruby--variable-values (vars)\n  \"Map VARS to a list of (variable value) pairs.\"\n  (mapcar (lambda (symbol) (list symbol (symbol-value symbol)))\n          vars))\n\n(defun enh-ruby--uptime-seconds ()\n  \"Return the number of seconds that Emacs has been running.\"\n  (float-time (time-subtract (current-time) before-init-time)))\n\n(defun enh-ruby-eval-file-local-variables ()\n  \"Re-evaluate file-local variables and reindent the file.\n\nReally only useful for my debugging sessions when I'm debugging\nstuff by changing vars over and over.\"\n  (interactive)\n  (hack-local-variables)\n  (indent-region (point-min) (point-max)))\n\n(defun enh-ruby--add-fl-variables (pairs)\n  (mapc (lambda (kv) (apply 'add-file-local-variable kv))\n        (enh-ruby--variable-values pairs)))\n\n(defun enh-ruby-add-file-local-variables ()\n  \"Insert all currently customized variables for this mode as\nfile-local variables. This is mainly for providing a complete\nexample in a bug report.\"\n  (interactive)\n  (enh-ruby--add-fl-variables (enh-ruby--changed-vars-with \"enh-ruby\")))\n\n(defun enh-ruby-add-all-file-local-variables ()\n  \"Insert all variables for this mode as file-local variables. This\nis mainly for providing a complete example in a bug report.\"\n  (interactive)\n  (enh-ruby--add-fl-variables (enh-ruby--all-vars-with \"enh-ruby\")))\n\n(defun enh-ruby-add-indent-file-local-variables ()\n  \"Insert all indent variables for this mode as file-local\nvariables. This is mainly for providing a complete example in a\nbug report.\"\n  (interactive)\n  (enh-ruby--add-fl-variables (enh-ruby--all-vars-with \"enh-ruby.*indent\")))\n\n(defun enh-ruby-del-file-local-variables ()\n  \"Delete all file-local-variables that aren't customized\"\n  (interactive)\n  (mapc #'delete-file-local-variable\n        (seq-difference (enh-ruby--all-vars-with \"enh-ruby\")\n                        (enh-ruby--changed-vars-with \"enh-ruby\"))))\n\n(defun enh-ruby-bug-report ()\n  \"Fill a buffer with data to make a ‘enh-ruby-mode’ bug report.\"\n  (interactive)\n  (with-help-window \"*enh-ruby-mode bug report*\"\n    (princ \"Please provide the following output in your bug report:\\n\")\n    (princ \"\\n\")\n    (let ((print-quoted t))\n      (pp (append `((emacs-uptime ,(enh-ruby--uptime-seconds))\n                    (mode-path    ,(find-library-name \"enh-ruby-mode\")))\n                  (enh-ruby--variable-values\n                   (append '(emacs-version system-type major-mode)\n                           (enh-ruby--changed-vars-with \"enh-ruby\"))))))\n    (princ \"\\n\")\n    (princ \"Also consider using enh-ruby-add-file-local-variables with any code you provide.\\n\\n\")\n    (princ \"Hit 'q' to close this buffer.\")))\n\n(erm-reset)\n\n(provide 'enh-ruby-mode)\n\n;;; enh-ruby-mode.el ends here\n"
  },
  {
    "path": "ruby/erm.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire_relative \"erm_buffer\"\n\nSTDIN.set_encoding \"UTF-8\"\n# STDIN.set_encoding \"BINARY\"\n\nclass BufferStore\n  def initialize\n    @buffers = {}\n  end\n\n  def get_buffer buf_num\n    @buffers[buf_num] ||= ErmBuffer.new if buf_num > 0\n  end\n\n  def rm buf_num\n    @buffers.delete buf_num\n  end\nend\n\nstore = BufferStore.new\n\nEOT = \"\\n\\0\\0\\0\\n\"\n\nbegin\n  while c = STDIN.gets(EOT)\n    cmd  = c[0].to_sym\n    args = c[1..-6].split \":\", 6\n    bn   = args.shift.to_i\n    buf  = store.get_buffer bn\n\n    case cmd\n    when :x then\n      (buf || ErmBuffer).set_extra_keywords args.first.split \" \"\n    when :c then\n      STDERR.print \"c\"\n      STDERR.puts \"#{buf.check_syntax}\\n\\n\\0\\0\\0\"\n    when :k then\n      store.rm bn\n    else\n      buf.add_content(cmd, *args) unless cmd == :g\n\n      unless cmd == :a\n        STDERR.puts buf.parse\n        STDERR.puts \"\\0\\0\\0\"\n      end\n    end\n  end\nrescue\n  STDERR.puts \"e#{$!.class}: #{$!.message}: #{$!.backtrace.join(\"\\n\")}#{EOT}\"\nend\n"
  },
  {
    "path": "ruby/erm_buffer.rb",
    "content": "require 'ripper'\n\nclass ErmBuffer\n  FONT_LOCK_NAMES = {\n    rem:             0,  # remove/ignore\n    sp:              0,\n    ident:           0,\n    tstring_content: 1,  # font-lock-string-face\n    const:           2,  # font-lock-type-face\n    ivar:            3,  # font-lock-variable-name-face\n    arglist:         3,\n    cvar:            3,\n    gvar:            3,\n    embexpr_beg:     3,\n    embexpr_end:     3,\n    comment:         4,  # font-lock-comment-face\n    embdoc:          4,\n    label:           5,  # font-lock-constant-face\n    CHAR:            6,  # font-lock-string-face\n    backtick:        7,  # ruby-string-delimiter-face\n    __end__:         7,\n    embdoc_beg:      7,\n    embdoc_end:      7,\n    tstring_beg:     7,\n    tstring_end:     7,\n    words_beg:       7,\n    regexp_beg:      8,  # ruby-regexp-delimiter-face\n    regexp_end:      8,\n    tlambda:         9,  # font-lock-function-name-face\n    defname:         9,\n    kw:              10, # font-lock-keyword-face\n    block:           10,\n    heredoc_beg:     11,\n    heredoc_end:     11,\n    op:              12, # ruby-op-face\n    regexp_string:   13, # ruby-regexp-face\n  }\n\n  module Adder\n    attr_accessor :statement_start\n    attr_accessor :ident\n    attr_accessor :first_token\n    attr_accessor :last_add\n\n    def nadd sym, tok, len = tok.size, ft = false, la = nil\n      d :nadd => [sym, tok, len, ft, la]\n      case sym\n      when :sp, :comment then\n        case parser.mode\n        when :predef, :expdef then\n          # do nothing\n        when :def\n          parser.mode = :postdef\n        else\n          parser.mode = nil\n        end\n      else\n        self.statement_start = false\n\n        parser.mode = :def if parser.mode == :predef\n        self.block = false if block == :b4args\n\n        case sym\n        when :ident, :const, :ivar, :gvar, :cvar then\n          self.ident = true\n        when :rem_rparen, :indent then\n          # leave alone?\n        else\n          d \"self.ident = false\"\n          self.ident = false\n        end\n      end\n\n      self.first_token = ft\n      self.last_add = la\n\n      target = parser.equal?(self) || lineno != parser.lineno ? self : prev\n      target.realadd sym, tok, len\n\n      sym\n    end\n  end # module Adder\n\n  class Heredoc\n    include Adder\n\n    attr_accessor :lineno, :lines, :parser, :prev, :tok\n    attr_accessor :block\n\n    def initialize parser, prev, tok, lineno\n      # TODO: tok?\n      self.parser = parser\n      self.prev   = prev\n      self.lineno = lineno\n      self.lines  = []\n      self.block  = nil\n    end\n\n    def d o\n      parser.d o\n    end\n\n    def realadd(*args)\n      lines << args\n    end\n\n    def restore\n      lines << [:heredoc_end, nil, nil] if lines.empty?\n      if parser.equal? prev then\n        for args in lines\n          parser.nadd(*args)\n        end\n        parser.heredoc = nil\n      else\n        prev.lines += lines\n        parser.heredoc = prev\n      end\n    end\n  end # class Heredoc\n\n  class Parser < ::Ripper   #:nodoc: internal use only\n    include Adder\n\n    # TODO: add prev_line and copy it when we clear line_so_far\n    # TODO: use this to calculate hanging indent for parenless args\n\n    # Indents:\n    #\n    # l - [, (, {, %w/%i open  or | goalpost open\n    # r - ], ), }, %w/%i close or | goalpost close\n    # b - begin/def/case/if\n    # e - end / embexpr (interpolation) end / close block }\n    # d - do / {\n    # s - statement start on BACKDENT_KW else/when/rescue etc\n    # c - continue - period followed by return (or other way around?)\n\n    INDENT_KW          = [:begin, :def, :case, :module, :class, :do, :for]\n    BACKDENT_KW        = [:elsif, :else, :when, :in, :rescue, :ensure]\n    BEGINDENT_KW       = [:if, :unless, :while, :until]\n    POSTCOND_KW        = [:if, :unless, :or, :and]\n    PRE_OPTIONAL_DO_KW = [:in, :while, :until]\n    DELIM_MAP          = { \"(\" => \")\", \"[\" => \"]\", \"{\" => \"}\" }\n\n    ESCAPE_LINE_END    = \"\\\\\\n\"\n\n    attr_accessor :heredoc, :mode\n    attr_accessor :indent_stack, :ident_stack, :brace_stack\n    attr_accessor :parser\n    attr_accessor :file_encoding\n    attr_accessor :ermbuffer\n    attr_accessor :point_min\n    attr_accessor :point_max\n    attr_accessor :src\n    attr_accessor :src_size\n    attr_accessor :first_count\n    attr_accessor :count\n    attr_accessor :res\n    attr_accessor :block\n    attr_accessor :list_count\n    attr_accessor :cond_stack\n    attr_accessor :plit_stack\n    attr_accessor :line_so_far\n\n    def initialize ermbuffer, src, point_min, point_max, first_count\n      self.ermbuffer     = ermbuffer\n      self.point_min     = point_min\n      self.point_max     = point_max\n      self.src           = src\n      self.src_size      = src.size\n      self.file_encoding = src.encoding\n      self.first_count   = nil\n      self.parser        = self # stupid hack for Adder module above\n      super src\n    end\n\n    def add(*args)\n      (heredoc || self).nadd(*args)\n    end\n\n    def indent type, c = 0\n      add :indent, type, c\n    end\n\n    # Bugs in Ripper:\n    # empty here doc fails to fire on_heredoc_end\n    def parse\n      self.count          = 1\n      self.mode           = nil\n      self.brace_stack    = []\n      self.heredoc        = nil\n      self.first_token    = true\n      self.last_add       = nil\n      self.res            = []\n      self.ident          = false\n      self.ident_stack    = []\n      self.block          = false\n      self.statement_start = true\n      self.indent_stack   = []\n      self.list_count     = 0\n      self.cond_stack     = []\n      self.plit_stack     = []\n      self.line_so_far    = []\n\n      catch :parse_complete do\n        super\n\n        realadd :rem_heredoc, '', src_size-count if heredoc\n      end\n\n      self.res = res.map.with_index { |v, i|\n        \"(%d %s)\" % [i, v.join(\" \")] if v\n      }\n\n      \"((%s %s %s %s)%s)\" % [src_size,\n                             point_min,\n                             point_max,\n                             indent_stack.join(' '),\n                             res.join]\n    end\n\n    def realadd sym, tok, len\n      if sym == :indent\n        pos = count + len\n        indent_stack << tok << pos if pos.between? point_min, point_max\n\n        return\n      end\n\n      start = count\n      throw :parse_complete if start > point_max\n\n      len = 2 + src.index(\"\\n\", start) - start unless len\n\n      pos = self.count += len\n      return if pos < point_min\n      start = point_min if start < point_min\n      pos = point_max   if pos   > point_max\n\n      sym = :rem if sym =~ /^rem_/\n      idx = FONT_LOCK_NAMES[sym]\n\n      if t = res[idx] then\n        if t.last == start then\n          t[-1] = pos\n        else\n          t << start << pos\n        end\n      else\n        res[idx] = [start, pos]\n      end\n\n      if (sym == :sp && tok == \"\\n\") || (sym == :comment && tok.end_with?(\"\\n\"))\n        line_so_far.clear\n      else\n        line_so_far << [sym, tok, len]\n      end\n\n      throw :parse_complete if pos == point_max\n    end\n\n    def maybe_plit_ending tok\n      if tok[-1] == plit_stack.last\n        plit_stack.pop\n\n        # Token can sometimes have preceding whitespace, which needs to be added\n        # as a separate token to work with indents.\n        if tok.length > 1\n          add :rem_end_ws, tok[0..-2]\n        end\n        indent :r\n        add :tstring_end, tok[-1]\n      end\n    end\n\n    ############################################################\n    # on_* handlers\n    # TODO: I don't like these generated methods. Harder to trace/debug.\n\n    def d o\n      ermbuffer.d o\n    end\n\n    def debug_on tok\n      return unless ermbuffer.debug\n      loc  = caller_locations.first.label.to_sym\n      rest = line_so_far.map {|a| a[1] }.join\n      d \"%-10s %p %p %p\" % [loc, tok, ident, rest]\n    end\n\n    [:CHAR, :__end__, :backtick, :embdoc, :embdoc_beg, :embdoc_end,\n     :label, :tlambda, :tstring_beg].each do |event|\n      define_method \"on_#{event}\" do |tok|\n        tok.force_encoding file_encoding if tok.encoding != file_encoding\n        debug_on tok\n        add event, tok\n      end\n    end\n\n    [:backref, :float, :int].each do |event|\n      define_method \"on_#{event}\" do |tok|\n        debug_on tok\n        add :\"rem_#{event}\", tok\n      end\n    end\n\n    [:cvar, :gvar, :ivar].each do |event|\n      define_method \"on_#{event}\" do |tok|\n        if mode == :sym then\n          debug_on [tok, :sym]\n          add :label, tok\n        else\n          debug_on [tok, :not_sym]\n          add event, tok\n        end\n      end\n    end\n\n    def on_comma tok\n      debug_on tok\n      self.mode = nil\n      r = add :rem_comma, tok, tok.size, false, list_count <= 0\n      self.statement_start = true\n      r\n    end\n\n    def on_comment tok\n      debug_on tok\n      on_eol :comment, tok\n    end\n\n    def on_const tok\n      debug_on [tok, mode]\n      case mode\n      when :sym then\n        self.mode = nil\n        add :label, tok\n      when :def, :predef then\n        r = add :const, tok\n        self.mode = :predef\n        r\n      else\n        add :const, tok\n      end\n    end\n\n    def on_embexpr_beg tok\n      debug_on tok\n      len = tok.size\n      if len > 2 then\n        add :tstring_content, tok, len - 2\n        len = 2\n      end\n\n      brace_stack << :embexpr\n      cond_stack << false\n      plit_stack << false\n\n      indent :d, 1\n      add :embexpr_beg, tok, len\n    end\n\n    def on_embexpr_end tok\n      debug_on tok\n      brace_stack.pop\n      cond_stack.pop\n      plit_stack.pop\n      indent :e\n      add :embexpr_beg, tok\n    end\n\n    def on_embvar tok\n      debug_on tok\n      len = tok.size\n      if len > 1 then\n        add :tstring_content, tok, len - 1\n        len = 1\n      end\n\n      add :ivar, tok, len\n    end\n\n    def on_eol sym, tok\n      debug_on tok\n      indent :c, tok.size if last_add\n\n      r = add sym, tok, tok.size, true\n\n      if heredoc && heredoc.lineno == lineno then\n        heredoc.restore\n      end\n\n      cond_stack.pop if cond_stack.last\n      self.statement_start = true\n\n      r\n    end\n\n    def on_heredoc_beg tok\n      debug_on tok\n      r = add :heredoc_beg, tok\n      if !heredoc || heredoc.lineno < lineno then\n        self.heredoc = Heredoc.new self, heredoc||self, tok, lineno\n      end\n      r\n    end\n\n    def on_heredoc_end tok\n      debug_on tok\n      add :heredoc_end, tok\n    end\n\n    def on_ident tok\n      debug_on [tok, mode]\n      case mode\n      when :sym then\n        add :label, tok\n      when :predef, :def then\n        add :defname, tok\n      when :period then\n        add :ident, tok\n      else\n        if ermbuffer.extra_keywords.include? tok then\n          add :kw, tok\n        else\n          add :ident, tok\n        end\n      end\n    end\n\n    def on_ignored_nl tok\n      debug_on tok\n      if tok then\n        on_nl tok\n      end\n    end\n\n    def on_kw sym # TODO: break up. 61 lines long\n      sym = sym.to_sym\n      debug_on [sym, mode]\n      case mode\n      when :sym then\n        add :label, sym\n      when :def, :predef then\n        if sym != :self then\n          add :defname, sym\n        else\n          r = add :kw, sym\n          self.mode = :def\n          r\n        end\n      else\n        last_add = nil\n\n        case sym\n        when :end then\n          indent :e\n        when :do then\n          if cond_stack.last then\n            cond_stack.pop\n            r = add :kw, sym\n          else\n            # `indent` precedes `add` for the compatibility of parsing result.\n            #\n            # `add` and `indent` must precede `self.block = :b4args`.\n            # Otherwise block is overwritten, and indentation is broken\n            # in the following code:\n            #  each do |a| # <- `|` for argument list is recognized as operator,\n            #      # <- which produces an extra indentation.\n            #  end\n\n            indent :d\n            r = add :kw, sym\n            self.block = :b4args\n          end\n\n          return r\n        when *BEGINDENT_KW then\n          if statement_start then\n            indent :b\n          elsif POSTCOND_KW.include? sym then\n            last_add = :cont\n          end\n        when *POSTCOND_KW then\n          last_add = :cont\n        when *INDENT_KW then\n          indent :b\n        when *BACKDENT_KW then\n          indent :s if statement_start\n        end\n\n        cond_stack << true if PRE_OPTIONAL_DO_KW.include? sym\n\n        r = add :kw, sym, sym.size, false, last_add\n        self.mode = :predef if [:def, :alias].include? sym\n        r\n      end\n    end\n\n    def on_lbrace tok\n      cond_stack << false\n      ident_stack << [ident, mode]\n\n      is_start_of_line = line_so_far.all? {|a| a[0] == :sp }\n      if ident && !is_start_of_line then\n        brace_stack << :block\n        indent :d\n        r = add :block, tok\n        self.block = :b4args\n        r\n      else\n        brace_stack << :brace\n        self.list_count += 1\n        indent :l\n        add :rem_lbrace, tok\n      end\n    end\n\n    def on_lparen tok\n      debug_on tok\n      newmode = case mode\n                when :def then\n                  self.mode = nil\n                  :def\n                when :predef then\n                  self.mode = :expdef\n                  :predef\n                else\n                  mode\n                end\n\n      ident_stack << [ident, newmode]\n      cond_stack << false\n      indent :l\n      self.list_count += 1\n      r = add :rem_lparen, tok\n      self.statement_start = true\n      r\n    end\n\n    def on_nl tok\n      on_eol :sp, tok\n    end\n\n    def on_op tok\n      if mode == :sym then\n         add :label, tok\n      else\n        r = if block && tok == '|'\n            case block\n            when :b4args then\n              indent :l\n              self.list_count += 1\n              self.block = :arglist\n            else\n              indent :r\n              self.list_count -= 1\n              self.block = false\n            end\n            add :arglist, tok, 1\n          else\n            case mode\n            when :def, :predef then\n              add :ident, tok\n            else\n              if mode == :postdef && tok == '='\n                indent :e\n              end\n              add :op, tok, tok.size, false, :cont\n            end\n          end\n        self.statement_start = true\n        r\n      end\n    end\n\n    def on_period tok\n      self.mode ||= :period\n\n      debug_on tok\n\n      indent :c, tok.size if tok == \"\\n\"\n      d :ident => ident\n      d :lsf => line_so_far\n\n      line_so_far_str = line_so_far.map {|a| a[1] }.join\n      if line_so_far_str.strip == \"\"\n        indent :c, -line_so_far_str.length\n      end\n\n      add :rem_period, tok, tok.size, false, :cont\n    end\n\n    def on_rbrace tok\n      debug_on tok\n      cond_stack.pop\n      type = case brace_stack.pop\n             when :embexpr then\n               indent :e\n               if plit_stack.last == false\n                 plit_stack.pop\n               end\n               :embexpr_beg\n             when :block then\n               indent :e\n               :block\n             when :brace then\n               indent :r\n               self.list_count -= 1\n               :rem_brace\n             else\n               :rem_other\n             end\n\n      add(type, tok).tap do\n        self.ident, self.mode = ident_stack.pop\n      end\n    end\n\n    def on_regexp_beg tok\n      tok.force_encoding file_encoding if tok.encoding != file_encoding\n      self.mode = :regexp\n      debug_on tok\n      add :regexp_beg, tok\n    end\n\n    def on_regexp_end tok\n      self.mode = nil\n      debug_on tok\n      add :regexp_end, tok\n    end\n\n    def on_rparen tok\n      debug_on tok\n      indent :r\n      r = add :rem_rparen, tok\n\n      self.list_count -= 1\n      self.ident, self.mode = ident_stack.pop\n      cond_stack.pop\n\n      r\n    end\n\n    def on_semicolon tok\n      debug_on tok\n      r = add :kw, :semicolon, 1, true\n      cond_stack.pop if cond_stack.last\n      self.statement_start = true\n      r\n    end\n\n    def on_sp tok\n      debug_on tok\n      if tok == ESCAPE_LINE_END then\n        indent :c, 2\n      end\n      add :sp, tok, tok.size, first_token, last_add\n    end\n\n    def on_symbeg tok\n      debug_on tok\n      r = add :label, tok\n      self.mode = :sym\n      r\n    end\n\n    def on_tlambeg tok\n      debug_on tok\n      brace_stack << :block\n      indent :d\n      add :block, tok\n    end\n\n    def on_tstring_content tok\n      debug_on tok\n      tok.force_encoding file_encoding if tok.encoding != file_encoding\n      if mode == :regexp\n        add :regexp_string, tok\n      elsif plit_stack.last # `tstring_content` is ignored by indent in emacs.\n        add :rem_tstring_content, tok # TODO: figure out this context? or collapse?\n      else\n        add :tstring_content, tok\n      end\n    end\n\n    def on_tstring_end tok\n      debug_on [tok, mode]\n\n      return if maybe_plit_ending(tok)\n\n      if mode == :sym then\n        add :label, tok\n      else\n        add :tstring_end, tok\n      end\n    end\n\n    def on_label_end tok\n      debug_on tok\n      add :tstring_beg, tok[0]\n      add :label, tok[1]\n    end\n\n    def on_words_beg tok\n      debug_on tok\n      delimiter = tok.strip[-1]  # ie. \"%w(\\n\" => \"(\"\n      plit_stack << (DELIM_MAP[delimiter] || delimiter)\n\n      indent :l\n      add :words_beg, tok\n    end\n\n    def on_words_sep tok\n      debug_on tok\n      return if maybe_plit_ending(tok)\n\n      add :rem_words_sep, tok\n    end\n\n    alias on_lbracket     on_lparen\n    alias on_qsymbols_beg on_words_beg\n    alias on_qwords_beg   on_words_beg\n    alias on_rbracket     on_rparen\n    alias on_symbols_beg  on_words_beg\n  end # class Parser\n\n  @@extra_keywords = {}\n\n  attr_writer :extra_keywords\n  attr_accessor :point_min\n  attr_accessor :point_max\n  attr_accessor :first_count\n  attr_accessor :buffer\n  attr_accessor :debug\n\n  def initialize\n    self.extra_keywords = nil\n    self.first_count    = nil\n    self.buffer         = ''\n    self.debug          = false\n  end\n\n  def d o\n    return unless debug\n    require \"pp\"\n    o = o.pretty_inspect unless String === o\n    puts o.gsub(/^/, \"     # \")\n  end\n\n  def add_content cmd, point_min, point_max, pbeg, len, content\n    self.point_min = point_min.to_i\n    self.point_max = point_max.to_i\n\n    pbeg = pbeg.to_i\n\n    self.first_count = pbeg if !first_count || pbeg < first_count\n\n    if cmd == :r || buffer.empty? then\n      self.buffer = content\n    else\n      len = pbeg + len.to_i - 2\n      if pbeg == 1 && len < 0 then\n        buffer[0..0] = content << buffer[0]\n      else\n        buffer[pbeg - 1..len] = content\n      end\n    end\n  end\n\n  # verify that this is used in erm.rb. I don't know how to trigger it. & args??\n  def check_syntax fname = '', code = buffer\n    $VERBOSE = true\n    # eval but do not run code\n    eval \"BEGIN{return}\\n#{code}\", nil, fname, 0\n  rescue SyntaxError\n    $!.message\n  rescue\n    # do nothing\n  ensure\n    $VERBOSE = nil\n  end\n\n  def parse\n    parser = ErmBuffer::Parser.new(self, buffer,\n                                   point_min, point_max,\n                                   first_count||0)\n    self.first_count = nil\n    parser.parse\n  end\n\n  def self.set_extra_keywords keywords\n    @@extra_keywords = Hash[keywords.map { |o| [o, true] }]\n  end\n\n  def set_extra_keywords keywords\n    self.extra_keywords = Hash[keywords.map { |o| [o, true] }]\n  end\n\n  def extra_keywords\n    @extra_keywords || @@extra_keywords\n  end\nend # class ErmBuffer\n"
  },
  {
    "path": "test/enh-ruby-mode-test.el",
    "content": "(eval-and-compile\n  (add-to-list 'load-path default-directory)\n  (load \"./helper\" nil t))\n\n(defun erm-run-current-test ()\n  (interactive)\n  (require 'ert)\n  (setq enh-tests nil)\n  (ert-delete-all-tests)\n  (load-file \"../enh-ruby-mode.el\")\n  (eval-buffer)\n  (let ((ert-debug-on-error t))\n    (ert-run-tests-interactively (lisp-current-defun-name))))\n\n(defun erm-run-all-tests ()\n  (interactive)\n  (require 'ert)\n  (setq enh-tests nil)\n  (ert-delete-all-tests)\n  (load-file \"../enh-ruby-mode.el\")\n  (eval-buffer)\n  (ert-run-tests-interactively t))\n\n(local-set-key (kbd \"C-c C-r\") #'erm-run-all-tests)\n(local-set-key (kbd \"C-c M-r\") #'erm-run-current-test)\n\n;; In batch mode, face-attribute returns 'unspecified,\n;; and it causes wrong-number-of-arguments errors.\n;; This is a workaround for it.\n(defun erm-darken-color (name)\n  (let ((attr (face-attribute name :foreground)))\n    (unless (equal attr 'unspecified)\n      (color-darken-name attr 20)\n      \"#000000\")))\n\n(enh-deftest enh-ruby-backward-sexp-test ()\n  (with-temp-enh-rb-string\n   \"def foo\\n  xxx\\nend\\n\"\n\n   (goto-char (point-max))\n   (enh-ruby-backward-sexp 1)\n   (line-should-equal \"def foo\")))\n\n(enh-deftest enh-ruby-backward-sexp-test--ruby ()\n  (with-temp-ruby-string\n   \"def foo\\n  xxx\\nend\\n\"\n\n   (goto-char (point-max))\n   (enh-ruby-backward-sexp 1)\n   (rest-of-line-should-equal \"def foo\")))\n\n(enh-deftest enh-ruby-backward-sexp-test-inner--erm ()\n  :expected-result :failed\n  (with-temp-enh-rb-string\n   \"def backward_sexp\\n  \\\"string #{expr \\\"another\\\"} word\\\"\\nend\\n\"\n\n   ;; DESIRED:\n   ;; def backward_sexp\\n  \\\"string #{expr \\\"another\\\"} word\\\"\\nend\\n\n   ;;                                                      ^ HERE\n   ;;                                                   ^ to here\n   ;;                                ^ to here\n   ;;                       ^ to here\n   ;; ^ to here\n\n   (search-forward \" word\")\n   (rest-of-line-should-equal \"\\\"\")\n\n   ;; DESIRED:\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"word\\\"\")\n   ;; (enh-ruby-backward-sexp)\n   ;; (rest-of-line-should-equal \"#{expr \\\"another\\\"} word\\\"\")\n   ;; (enh-ruby-backward-sexp)\n   ;; (rest-of-line-should-equal \"\\\"string #{expr \\\"another\\\"} word\\\"\")\n   ;; (enh-ruby-backward-sexp)\n   ;; (rest-of-line-should-equal \"def backward_sexp\")\n\n   ;; CURRENT:\n   ;; def backward_sexp\\n  \\\"string #{expr \\\"another\\\"} word\\\"\\nend\\n\n   ;;                                                      ^ HERE\n   ;;                                ^ to here\n   ;; ^ to here\n\n   ;; CURRENT:\n\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"{expr \\\"another\\\"} word\\\"\")\n\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"def backward_sexp\")\n   ))\n\n(enh-deftest enh-ruby-backward-sexp-test-inner--ruby ()\n  :expected-result :failed\n  (with-temp-ruby-string\n   \"def backward_sexp\\n  \\\"string #{expr \\\"another\\\"} word\\\"\\nend\\n\"\n   ;;                                                      ^ here\n   ;;                     ^ to here\n   ;;^ NOT HERE\n   (search-forward \" word\")\n   ;; (move-end-of-line nil)\n\n   (rest-of-line-should-equal \"\\\"\")\n\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"word\\\"\")\n\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"{expr \\\"another\\\"} word\\\"\")\n\n   (enh-ruby-backward-sexp)\n   (rest-of-line-should-equal \"string #{expr \\\"another\\\"} word\\\"\")\n\n   ;; this blows out: (scan-error \"Containing expression ends prematurely\" 21 21)\n   ;; (enh-ruby-backward-sexp)\n   ;; (rest-of-line-should-equal \"\\\"string #{expr \\\"another\\\"} word\\\"\")\n   ))\n\n(enh-deftest enh-ruby-forward-sexp-test ()\n  (with-temp-enh-rb-string\n   \"def foo\\n  xxx\\n end\\n\\ndef backward_sexp\\n  xxx\\nend\\n\"\n\n   (enh-ruby-forward-sexp 1)\n   (forward-char 2)\n   (rest-of-line-should-equal \"def backward_sexp\")))\n\n(enh-deftest enh-ruby-up-sexp-test ()\n  (with-temp-enh-rb-string\n   \"def foo\\n  %_bosexp#{sdffd} test1_[1..4].si\\nend\"\n\n   (search-forward \"test1_\")\n   (enh-ruby-up-sexp)\n   (line-should-equal \"def foo\")))      ; maybe this should be %_bosexp?\n\n(enh-deftest enh-ruby-end-of-defun ()\n  (with-temp-enh-rb-string\n   \"class Class\\ndef method\\n# blah\\nend # method\\nend # class\"\n\n   (search-forward \"blah\")\n   (enh-ruby-end-of-defun)\n   (rest-of-line-should-equal \" # method\")))\n\n(enh-deftest enh-ruby-end-of-block ()\n  (with-temp-enh-rb-string\n   \"class Class\\ndef method\\n# blah\\nend # method\\nend # class\"\n\n   (search-forward \"blah\")\n   (enh-ruby-end-of-block)\n   (rest-of-line-should-equal \" # method\")))\n\n;;; indent-region\n\n(enh-deftest enh-ruby-indent-array-of-strings ()\n  (with-deep-indent nil\n    (string-should-indent \"words = [\\n'moo'\\n]\\n\"\n                          \"words = [\\n  'moo'\\n]\\n\")))\n\n(enh-deftest enh-ruby-indent-array-of-strings-incl-first ()\n  (with-deep-indent nil\n    (string-should-indent \"words = ['cow',\\n'moo'\\n]\\n\"\n                          \"words = ['cow',\\n  'moo'\\n]\\n\")))\n\n(enh-deftest enh-ruby-indent-array-of-strings/deep ()\n  (with-deep-indent t\n    (string-should-indent \"words = ['cow',\\n'moo'\\n]\\n\"\n                          \"words = ['cow',\\n         'moo'\\n        ]\\n\")))\n\n(enh-deftest enh-ruby-indent-array-of-strings-incl-first/deep ()\n  (with-deep-indent t\n    (string-should-indent \"words = ['cow',\\n'moo'\\n]\\n\"\n                          \"words = ['cow',\\n         'moo'\\n        ]\\n\")))\n\n(enh-deftest enh-ruby-indent-array-of-strings/ruby ()\n  (string-should-indent-like-ruby \"words = [\\n'moo'\\n]\\n\"))\n\n(enh-deftest enh-ruby-indent-array-of-strings-incl-first/ruby ()\n  (string-should-indent-like-ruby \"words = ['cow',\\n'moo'\\n]\\n\"\n                                  'deep))\n\n(enh-deftest enh-ruby-indent-not-method ()\n  (string-should-indent-like-ruby\n   \"\\nclass Object\\ndef !\\n100\\nend\\nend\"))\n\n(enh-deftest enh-ruby-indent-hanging-period-after-parens ()\n  (string-should-indent-like-ruby\n   \":a\\n(b)\\n.c\"))\n\n(enh-deftest enh-ruby-indent-hanging-period ()\n  (string-should-indent-like-ruby\n   \":a\\nb\\n.c\"))\n\n(enh-deftest enh-ruby-indent-def-endless ()\n  (with-deep-indent nil\n   (string-should-indent \"class Foo\\ndef foo = z\\ndef bar = y\\nend\\n\"\n                         \"class Foo\\n  def foo = z\\n  def bar = y\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-def-endless/params ()\n  (with-deep-indent nil\n   (string-should-indent \"class Foo\\ndef foo(a) = z\\ndef bar = y\\nend\\n\"\n                         \"class Foo\\n  def foo(a) = z\\n  def bar = y\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-def-endless/args-forward ()\n  (with-deep-indent nil\n   (string-should-indent \"class Foo\\ndef foo(...) = z\\ndef bar = y\\nend\\n\"\n                         \"class Foo\\n  def foo(...) = z\\n  def bar = y\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-def-after-private ()\n  (with-deep-indent nil\n   (string-should-indent \"class Foo\\nprivate def foo\\nx\\nend\\nend\\n\"\n                         \"class Foo\\n  private def foo\\n    x\\n  end\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-def-after-private/deep ()\n  (with-deep-indent t\n   (string-should-indent \"class Foo\\nprivate def foo\\nx\\nend\\nend\\n\"\n                         \"class Foo\\n  private def foo\\n            x\\n          end\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-hash ()\n  ;; https://github.com/zenspider/enhanced-ruby-mode/issues/78\n  (with-deep-indent nil\n    (string-should-indent \"c = {\\na: a,\\nb: b\\n}\\n\"\n                          \"c = {\\n  a: a,\\n  b: b\\n}\\n\")))\n\n(defconst input/indent-hash/trail \"\\nc = {a: a,\\nb: b,\\n c: c\\n}\")\n(defconst input/indent-hash/hang  \"\\nc = {\\na: a,\\nb: b,\\n c: c\\n}\")\n(defconst exp/indent-hash/hang    \"\\nc = {\\n  a: a,\\n  b: b,\\n  c: c\\n}\")\n(defconst exp/indent-hash/trail   \"\\nc = {a: a,\\n     b: b,\\n     c: c\\n    }\")\n(defconst exp/indent-hash/trail/8 \"\\nc = {a: a,\\n             b: b,\\n             c: c\\n    }\")\n\n(defmacro with-bounce-and-hang (bounce indent1 indent2 &rest body)\n  `(let ((enh-ruby-bounce-deep-indent              ,bounce)\n         (enh-ruby-deep-indent-paren               t)\n         (enh-ruby-hanging-brace-indent-level      (or ,indent1\n                                                       enh-ruby-hanging-brace-indent-level))\n         (enh-ruby-hanging-brace-deep-indent-level (or ,indent2\n                                                       enh-ruby-hanging-brace-deep-indent-level)))\n     ,@body))\n(put 'with-bounce-and-hang 'lisp-indent-function 'defun)\n\n(enh-deftest enh-ruby-indent-hash/deep/hang/def ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil nil nil\n      (string-should-indent input/indent-hash/hang\n                            exp/indent-hash/hang))))\n\n(enh-deftest enh-ruby-indent-hash/deep/bounce/hang/def ()\n  (with-deep-indent t\n    (with-bounce-and-hang t nil nil\n      (string-should-indent input/indent-hash/hang\n                            \"\\nc = {\\n     a: a,\\n     b: b,\\n     c: c\\n    }\"))))\n\n;; if bounce off, hanging-brace-deep-indent-level doesn't matter\n(enh-deftest enh-ruby-indent-hash/deep/hang/99 ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil nil 99\n      (string-should-indent input/indent-hash/hang\n                            exp/indent-hash/hang))))\n\n;; 3 < 4, so close brace is at 1\n(enh-deftest enh-ruby-indent-hash/deep/hang/bil-3 ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil 3 nil\n      (string-should-indent input/indent-hash/hang\n                            \"\\nc = {\\n   a: a,\\n   b: b,\\n   c: c\\n}\"))))\n\n;; 8 > 4, so close brace is at 4\n(enh-deftest enh-ruby-indent-hash/deep/hang/bil-8 ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil 8 nil\n      (string-should-indent input/indent-hash/hang\n                            \"\\nc = {\\n        a: a,\\n        b: b,\\n        c: c\\n    }\"))))\n\n(enh-deftest enh-ruby-indent-hash/deep/trail/def ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil nil nil\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail))))\n\n(enh-deftest enh-ruby-indent-hash/deep/bounce/trail/def ()\n  (with-deep-indent t\n    (with-bounce-and-hang t nil nil\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail))))\n\n(enh-deftest enh-ruby-indent-hash/deep/trail/3 ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil 3 8\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail/8))))\n\n(enh-deftest enh-ruby-indent-hash/deep/bounce/trail/3 ()\n  (with-deep-indent t\n    (with-bounce-and-hang t 3 8\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail/8))))\n\n(enh-deftest enh-ruby-indent-hash/deep/trail/8 ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil 8 8\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail/8))))\n\n(enh-deftest enh-ruby-indent-hash/deep/bounce/trail/8 ()\n  (with-deep-indent t\n    (with-bounce-and-hang t 8 8\n      (string-should-indent input/indent-hash/trail\n                            exp/indent-hash/trail/8))))\n\n(enh-deftest enh-ruby-indent-bug/90/a ()\n  (string-should-indent-like-ruby \"aa.bb(:a => 1,\\n      :b => 2,\\n      :c => 3)\\n\"\n                                  'deep))\n\n(enh-deftest enh-ruby-indent-bug/90/b ()\n  (string-should-indent-like-ruby \"literal_array = [\\n  :a,\\n  :b,\\n  :c\\n]\\n\"))\n\n(enh-deftest enh-ruby-indent-hash-after-cmd ()\n  (with-deep-indent nil\n    (string-should-indent \"x\\n{\\na: a,\\nb: b\\n}\"\n                          \"x\\n{\\n  a: a,\\n  b: b\\n}\")))\n\n(enh-deftest enh-ruby-indent-hash-after-cmd/deep ()\n  (with-deep-indent t\n    (string-should-indent \"x\\n{\\na: a,\\nb: b\\n}\"\n                          \"x\\n{\\n  a: a,\\n  b: b\\n}\")))\n\n(enh-deftest enh-ruby-indent-hash-after-cmd/ruby ()\n  (string-should-indent-like-ruby \"x\\n{\\na: a,\\nb: b\\n}\"))\n\n(enh-deftest enh-ruby-indent-if-in-assignment ()\n  (with-deep-indent nil\n    (string-should-indent \"foo = if bar\\nx\\nelse\\ny\\nend\\n\"\n                          \"foo = if bar\\n  x\\nelse\\n  y\\nend\\n\")))\n\n(enh-deftest enh-ruby-indent-if-in-assignment/deep ()\n  (with-deep-indent t\n    (string-should-indent \"foo = if bar\\nx\\nelse\\ny\\nend\\n\"\n                          \"foo = if bar\\n        x\\n      else\\n        y\\n      end\\n\")))\n\n(enh-deftest enh-ruby-indent-leading-dots ()\n  (string-should-indent \"d.e\\n.f\\n\"\n                        \"d.e\\n  .f\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-cvar ()\n  (string-should-indent \"@@b\\n.c\\n.d\\n\"\n                        \"@@b\\n  .c\\n  .d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-cvar/ruby ()\n  (string-should-indent-like-ruby \"@@b\\n.c\\n.d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-gvar ()\n  (string-should-indent \"$b\\n.c\\n.d\\n\"\n                        \"$b\\n  .c\\n  .d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-gvar/ruby ()\n  (string-should-indent-like-ruby \"$b\\n.c\\n.d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-ident ()\n  (string-should-indent \"b\\n.c\\n.d\\n\"\n                        \"b\\n  .c\\n  .d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-ident/ruby ()\n  (string-should-indent-like-ruby \"b\\n.c\\n.d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-ivar ()\n  (string-should-indent \"@b\\n.c\\n.d\\n\"\n                        \"@b\\n  .c\\n  .d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-ivar/ruby ()\n  (string-should-indent-like-ruby \"@b\\n.c\\n.d\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-block ()\n  (string-should-indent \"a\\n.b {}\\n.c\\n\"\n                        \"a\\n  .b {}\\n  .c\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-block/ruby ()\n  (string-should-indent-like-ruby \"a\\n.b {}\\n.c\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-comment ()\n  (string-should-indent \"a\\n.b # comment\\n.c\\n\"\n                        \"a\\n  .b # comment\\n  .c\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-comment/ruby ()\n  (string-should-indent-like-ruby \"a\\n.b # comment\\n.c\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots/ruby ()\n  (string-should-indent-like-ruby \"d.e\\n.f\\n\"))\n\n(defconst leading-dot-input  \"\\na\\n.b\\n.c(\\nd,\\ne\\n)\\n.f\\n\")\n(defconst trailing-dot-input \"\\na.\\nb.\\nc(\\nd,\\ne\\n).\\nf\\n\")\n\n(enh-deftest enh-ruby-indent-leading-dots-with-arguments-and-newlines ()\n  (string-should-indent leading-dot-input\n                        \"\\na\\n  .b\\n  .c(\\n    d,\\n    e\\n  )\\n  .f\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-arguments-and-newlines/bounce ()\n  (with-bounce-and-hang t nil nil\n    (string-should-indent leading-dot-input\n                          \"\\na\\n  .b\\n  .c(\\n     d,\\n     e\\n    )\\n  .f\\n\")))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-arguments-and-newlines/ruby ()\n  (string-should-indent-like-ruby leading-dot-input))\n\n(enh-deftest enh-ruby-indent-trailing-dots-with-arguments-and-newlines ()\n  (string-should-indent trailing-dot-input\n                        \"\\na.\\n  b.\\n  c(\\n    d,\\n    e\\n  ).\\n  f\\n\"))\n\n(enh-deftest enh-ruby-indent-trailing-dots-with-arguments-and-newlines/bounce ()\n  (with-bounce-and-hang t nil nil\n    (string-should-indent trailing-dot-input\n                          \"\\na.\\n  b.\\n  c(\\n    d,\\n    e\\n   ).\\n  f\\n\")))\n\n(enh-deftest enh-ruby-indent-trailing-dots-with-arguments-and-newlines/ruby ()\n  (string-should-indent-like-ruby trailing-dot-input))\n\n(enh-deftest enh-ruby-add-log-current-method/nested-modules ()\n  :expected-result :failed\n  (with-temp-enh-rb-string\n   \"module One\\nmodule Two\\nclass Class\\ndef method\\n# blah\\nend # method\\nend # class\\nend # One\\nend # Two\"\n   (search-forward \"blah\")\n   (should (equal \"One::Two::Class#method\" (enh-ruby-add-log-current-method)))))\n\n(enh-deftest enh-ruby-add-log-current-method/compact-modules ()\n  (with-temp-enh-rb-string\n   \"class One::Two::Class\\ndef method\\n# blah\\nend # method\\nend # class\"\n   (search-forward \"blah\")\n   (should (equal \"One::Two::Class#method\" (enh-ruby-add-log-current-method)))))\n\n(enh-deftest enh-ruby-indent-continued-assignment ()\n  (string-should-indent \"\\na =\\nb.map do |c|\\nd(c)\\nend\\n\"\n                        \"\\na =\\n  b.map do |c|\\n    d(c)\\n  end\\n\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-block-and-newlines ()\n  (string-should-indent \"\\na\\n.b do\\nc\\nend\\n.d\\n\\ne\"\n                        \"\\na\\n  .b do\\n    c\\n  end\\n  .d\\n\\ne\"))\n\n(enh-deftest enh-ruby-indent-leading-dots-with-brackets-and-newlines ()\n  (string-should-indent \"\\na\\n.b {\\nc\\n}\\n.d\\n\\ne\"\n                        \"\\na\\n  .b {\\n    c\\n  }\\n  .d\\n\\ne\"))\n\n(enh-deftest enh-ruby-indent-not-on-eol-opening/deep ()\n  (with-deep-indent t\n   (string-should-indent \"\\nfoo(:bar,\\n:baz)\\nfoo(\\n:bar,\\n:baz,\\n)\\n[:foo,\\n:bar]\\n[\\n:foo,\\n:bar\\n]\"\n                         \"\\nfoo(:bar,\\n    :baz)\\nfoo(\\n  :bar,\\n  :baz,\\n)\\n[:foo,\\n :bar]\\n[\\n  :foo,\\n  :bar\\n]\")))\n\n(enh-deftest enh-ruby-indent-pct-w-array ()\n  (with-deep-indent nil\n    (string-should-indent \"words = %w[\\na\\nb\\n]\\n\"\n                          \"words = %w[\\n  a\\n  b\\n]\\n\")))\n\n(enh-deftest enh-ruby-indent-pct-w-array/deep ()\n  (with-deep-indent t\n    (with-bounce-and-hang nil nil nil\n     (string-should-indent \"\\nwords = %w[a\\nb\\nc\\n]\\n\"\n                           \"\\nwords = %w[a\\n           b\\n           c\\n          ]\\n\"))))\n\n;; NO! ruby-mode refuses to indent %w at all\n;; words = %w[ a\n;; b\n;; c\n;; ]\n;;\n;; vs enh-ruby-mode:\n;;\n;; words = %w[ a\n;;   b\n;;   c\n;; ]\n;;\n;; and w/ deep-indent-paren t:\n;; words = %w[ a\n;;             b\n;;             c\n;;           ]\n;;\n;; (enh-deftest enh-ruby-indent-pct-w-array/ruby ()\n;;   (string-should-indent-like-ruby \"words = %w[ a\\nb\\nc\\n]\\n\"))\n\n(enh-deftest enh-ruby-indent-trailing-dots ()\n  (string-should-indent \"a.b.\\nc\\n\"\n                        \"a.b.\\n  c\\n\"))\n\n(enh-deftest enh-ruby-indent-trailing-dots/ruby ()\n  (string-should-indent-like-ruby \"a.b.\\nc\\n\"))\n\n;;; indent-for-tab-command -- seems different than indent-region in some places\n\n(enh-deftest enh-ruby-beginning-of-block ()\n  (with-temp-enh-rb-string\n   \"RSpec.describe Foo do\\n  it 'bar' do\\n    HERE\\n  end\\nend\"\n\n   (search-forward \"HERE\")\n\n   (enh-ruby-beginning-of-block)\n   (line-should-equal \"  it 'bar' do\")\n\n   (enh-ruby-beginning-of-block)\n   (line-should-equal \"RSpec.describe Foo do\")\n\n   (enh-ruby-beginning-of-block)\n   (line-should-equal \"RSpec.describe Foo do\")))\n\n(enh-deftest enh-ruby-indent-for-tab-heredocs/off ()\n  (with-temp-enh-rb-string\n   \"meth <<-DONE\\n  a b c\\nd e f\\nDONE\\n\"\n\n   (search-forward \"d e f\")\n   (move-beginning-of-line nil)\n   (let ((enh-ruby-preserve-indent-in-heredocs nil))\n     (indent-for-tab-command)           ; hitting TAB char\n     (buffer-should-equal \"meth <<-DONE\\n  a b c\\nd e f\\nDONE\\n\"))))\n\n(enh-deftest enh-ruby-indent-for-tab-heredocs/on ()\n  (with-temp-enh-rb-string\n   \"meth <<-DONE\\n  a b c\\nd e f\\nDONE\\n\"\n\n   (search-forward \"d e f\")\n   (move-beginning-of-line nil)\n   (let ((enh-ruby-preserve-indent-in-heredocs t))\n     (indent-for-tab-command)           ; hitting TAB char\n     (buffer-should-equal \"meth <<-DONE\\n  a b c\\n  d e f\\nDONE\\n\"))))\n\n(enh-deftest enh-ruby-indent-for-tab-heredocs/unset ()\n  (with-temp-enh-rb-string\n   \"meth <<-DONE\\n  a b c\\nd e f\\nDONE\\n\"\n\n   (search-forward \"d e f\")\n   (move-beginning-of-line nil)\n   (indent-for-tab-command)             ; hitting TAB char\n   (buffer-should-equal \"meth <<-DONE\\n  a b c\\nd e f\\nDONE\\n\")))\n\n;;; enh-ruby-toggle-block\n\n(defconst ruby-do-block      \"7.times do |i|\\n  puts \\\"number #{i+1}\\\"\\nend\\n\")\n(defconst ruby-brace-block/1 \"7.times { |i| puts \\\"number #{i+1}\\\" }\\n\")\n(defconst ruby-brace-block/3 \"7.times { |i|\\n  puts \\\"number #{i+1}\\\"\\n}\\n\")\n\n(defun enh-ruby-toggle-block-and-wait ()\n  (enh-ruby-toggle-block)\n  (erm-wait-for-parse)\n  (font-lock-ensure))\n\n(defun toggle-to-do ()\n  (enh-ruby-toggle-block-and-wait)\n  (buffer-should-equal ruby-do-block))\n\n(defun toggle-to-brace ()\n  (enh-ruby-toggle-block-and-wait)\n  (buffer-should-equal ruby-brace-block/1))\n\n(enh-deftest enh-ruby-toggle-block/both ()\n  (with-temp-enh-rb-string ruby-brace-block/3\n    (toggle-to-do)\n    (toggle-to-brace)))\n\n(enh-deftest enh-ruby-toggle-block/brace ()\n  (with-temp-enh-rb-string ruby-brace-block/3\n    (toggle-to-do)))\n\n(enh-deftest enh-ruby-toggle-block/do ()\n  (with-temp-enh-rb-string ruby-do-block\n    (toggle-to-brace)))\n\n(defconst ruby-brace-block/puts \"7.times { |i| puts i }\\n\")\n(defconst ruby-do-block/puts    \"7.times do |i|\\n  puts i \\nend\\n\")\n\n(enh-deftest enh-ruby-toggle-block/does-not-trigger-when-point-is-beyond-block ()\n  (with-temp-enh-rb-string ruby-brace-block/puts\n    (search-forward \"}\")\n    (enh-ruby-toggle-block-and-wait)\n    (buffer-should-equal ruby-brace-block/puts)))\n\n(enh-deftest enh-ruby-toggle-block/triggers-when-point-is-at-end-of-block ()\n  (with-temp-enh-rb-string ruby-brace-block/puts\n    (search-forward \"}\")\n    (backward-char)\n    (enh-ruby-toggle-block-and-wait)\n    (buffer-should-equal ruby-do-block/puts)))\n\n(defconst ruby-puts \"puts \\\"test\\\"\")\n\n(enh-deftest enh-ruby-toggle-block/with-no-block-in-buffer-does-not-fail ()\n  (with-temp-enh-rb-string ruby-puts\n    (enh-ruby-toggle-block-and-wait)\n    (buffer-should-equal ruby-puts)))\n\n(defconst ruby-brace/let \"let(:dont_let) { { a: 1, b: 2 } }\\n\")\n(defconst ruby-do/let    \"let(:dont_let) do\\n  { a: 1, b: 2 } \\nend\\n\")\n\n(enh-deftest enh-ruby-toggle-block/brace-with-inner-hash ()\n  (with-temp-enh-rb-string ruby-brace/let\n    (enh-ruby-toggle-block-and-wait)\n    (buffer-should-equal ruby-do/let)))\n\n(enh-deftest enh-ruby-paren-mode-if/open ()\n  (should-show-parens\n   \"\nG|ifG foo\n  bar\nGendG\"))\n\n(enh-deftest enh-ruby-paren-mode-if/close ()\n  (should-show-parens\n   \"\nGifG foo\n  bar\nGend|G\"))\n\n(enh-deftest enh-ruby-paren-mode-if/mismatch ()\n  (should-show-parens\n   \"\nR|ifR foo\n  bar\nR}R\"))\n\n(enh-deftest enh-ruby-paren-mode-while-do/open ()\n  (should-show-parens\n   \"\nG|whileG foo do\n  if bar\n    baz\n  end\nGendG\"))\n\n(enh-deftest enh-ruby-paren-mode-while-do/close ()\n  (should-show-parens\n   \"\nGwhileG foo do\n  if bar\n    baz\n  end\nGend|G\"))\n\n(enh-deftest enh-ruby-paren-mode-while-do/mismatch ()\n  (should-show-parens\n   \"\nR|whileR foo do\n  if bar\n    baz\n  end\nRR\"))\n\n(enh-deftest enh-ruby-paren-mode-begin-end ()\n  (should-show-parens\n   \"\nG|beginG\n  foo\nrescue\nGendG\"))\n\n(enh-deftest enh-ruby-paren-mode-if-dont-show ()\n  \"point is not in right spot to highlight pairs so nothing\nshould be tagged\"\n  (should-show-parens\n   \"\ni|f foo\n  bar\nend\")\n  (should-show-parens\n   \"\nif| foo\n  bar\nend\")\n  (should-show-parens\n   \"\nif foo\n  bar\nen|d\")\n  (should-show-parens\n   \"\nif foo\n  bar\ne|nd\"))\n\n(enh-deftest enh-ruby-paren-mode-delegate ()\n  \"delegate braces to show-paren-data-function (i.e. don't\nhighlight anything)\"\n  (should-show-parens\n   \"foo.map G|{G there G}G\"))\n"
  },
  {
    "path": "test/helper.el",
    "content": "(require 'ert)\n(require 'ert-x)\n(require 'paren)                        ; for show-paren tests & helper\n(eval-and-compile\n  (add-to-list 'load-path (file-name-directory (directory-file-name default-directory))))\n(require 'enh-ruby-mode)\n\n;; I hate this so much... Shuts up \"Indenting region...\" output\n(defun make-progress-reporter (&rest ignored) nil)\n\n(defvar enh-tests '())\n\n;; turns out I had a duplicate test and it was driving me crazy. This is my fix.\n(defmacro enh-deftest (name &rest rest)\n  (if (memq name enh-tests)\n      (error \"Duplicate test name! %S\" name)\n    (setq enh-tests (cons name enh-tests))\n    `(ert-deftest ,name ,@rest)))\n(put 'enh-deftest 'lisp-indent-function 'defun)\n\n(defmacro with-temp-enh-rb-string (str &rest body)\n  `(with-temp-buffer\n     (insert ,str)\n     (enh-ruby-mode)\n     (erm-wait-for-parse)\n     (font-lock-ensure)\n     (goto-char (point-min))\n     (progn ,@body)))\n(put 'with-temp-enh-rb-string 'lisp-indent-function 1)\n\n(defmacro with-temp-ruby-string (str &rest body)\n  `(with-temp-buffer\n     (insert ,str)\n     (ruby-mode)\n     (font-lock-ensure)\n     (goto-char (point-min))\n     (progn ,@body)))\n\n(defmacro with-deep-indent (deep? &rest body)\n  `(let ((enh-ruby-deep-indent-construct ,deep?) ; def / if\n         (enh-ruby-deep-indent-paren ,deep?))    ; arrays / hashes\n     ,@body))\n(put 'with-deep-indent 'lisp-indent-function 1)\n\n(defun buffer-string-plain ()\n  (buffer-substring-no-properties (point-min) (point-max)))\n\n(defun string-plain (s)\n  (substring-no-properties s))\n\n(defun string-should-indent (ruby exp)\n  (let ((act (with-temp-enh-rb-string ruby (ert-buffer-string-reindented))))\n   (should (equal exp (string-plain act)))))\n\n(defun string-should-indent-like-ruby (ruby &optional deep?)\n  (with-deep-indent deep?\n    (let ((exp (with-temp-ruby-string   ruby (ert-buffer-string-reindented)))\n          (act (with-temp-enh-rb-string ruby (ert-buffer-string-reindented))))\n      (should (equal (string-plain exp) (string-plain act))))))\n\n(defun buffer-should-equal (exp)\n  (should (equal exp (buffer-string-plain))))\n\n(defun rest-of-line-should-equal (exp)\n  (should (equal exp (rest-of-line))))\n\n(defun line-should-equal (exp)\n  (should (equal exp (all-of-line))))\n\n(defun all-of-line ()\n  (save-excursion\n    (move-beginning-of-line nil)\n    (let ((start (point)))\n      (end-of-line)\n      (buffer-substring-no-properties start (point)))))\n\n(defun rest-of-line ()\n  (save-excursion\n   (let ((start (point)))\n     (end-of-line)\n     (buffer-substring-no-properties start (point)))))\n\n(defun should-show-parens (contents)\n  \"CONTENTS is a template specifying expected paren highlighting.\nGfooG means expect foo be green (matching parens), RfooR means\nred (mismatched parens), and | is point. No G/R tags means expect\nno erm highlighting (i.e. delegate to normal paren-mode)\"\n  (with-temp-buffer\n    (insert contents)\n    (goto-char (point-min))\n    (let ((case-fold-search nil) (tags ()) point-pos mismatch)\n      (while (re-search-forward \"[GR|]\" nil t)\n        (let ((found-char (char-before)))\n          (backward-delete-char 1)\n          (cond\n           ((char-equal found-char ?G) (push (point) tags))\n           ((char-equal found-char ?R) (progn (push (point) tags) (setq mismatch t)))\n           ((char-equal found-char ?|) (setq point-pos (point))))))\n      (setq tags (nreverse tags))\n      (when (and tags (< (abs (- point-pos (nth 3 tags))) (abs (- point-pos (car tags)))))\n        (setq tags (list (nth 2 tags) (nth 3 tags) (nth 0 tags) (nth 1 tags))))\n      (setq contents (buffer-substring (point-min) (point-max)))\n      (with-temp-enh-rb-string\n       contents\n       (goto-char point-pos)\n       (should\n        (equal\n         (erm-show-paren-data-function)\n         (if tags (append tags `(,mismatch)) nil)))))))\n"
  },
  {
    "path": "test/helper.rb",
    "content": "require_relative './markup'\n\nmodule ErmTestHelper\n  module_function\n\n  # workaround for Minitest::Assertions.assert_parse\n  # to remove surrounding `\"` from markup diff\n  def override_inspect(obj)\n    def obj.inspect\n      self\n    end\n    obj\n  end\nend\n\nmodule Minitest::Assertions\n  module_function\n\n  def assert_parse(markedup_code, buf = ErmBuffer.new, msg = nil)\n    markedup_code = markedup_code.gsub(/\\A\\n|\\n\\z/, \"\")\n\n    expected_sexp, code = Markup.parse_markup(markedup_code)\n    actual_sexp = Markup.parse_code(code, buf)\n\n    msg = message(msg || code, \"\") do\n      expected_markup = markedup_code\n      actual_markup   = Markup.markup(code, Markup.parse_sexp(actual_sexp))\n\n      diff_markup = diff(ErmTestHelper.override_inspect(expected_markup),\n                         ErmTestHelper.override_inspect(actual_markup))\n      diff_sexp   = diff(expected_sexp, actual_sexp)\n\n      diff_markup + \"\\n\" + diff_sexp\n    end\n\n    assert(expected_sexp == actual_sexp, msg)\n  end\nend\n"
  },
  {
    "path": "test/markup.rb",
    "content": "# -*- coding: utf-8 -*-\nrequire 'strscan'\nrequire_relative '../ruby/erm_buffer'\n\nmodule Markup\n  module_function\n\n  def parse_code(code, erm_buffer = ErmBuffer.new)\n    erm_buffer.add_content(:r, 1, code.size + 1, 0, code.size, code)\n    erm_buffer.parse\n  end\n\n  # Parses marked-up code and returns [raw_sexp, code]\n  # raw_sexp follows the same format as the return value of ErmBuffer::Parser#parse.\n  def parse_markup(str)\n    code = +\"\"\n    indent_stack = []\n    result = Hash.new { |h, k| h[k] = [] }\n    last_tag = nil\n\n    ss = StringScanner.new(str)\n    loop do\n      case\n      when ss.scan(/«@(.)»/)\n        indent_stack << ss[1] << code.size + 1\n      when ss.scan(/«(\\w+)»/)\n        last_tag << code.size + 1 if last_tag && last_tag.size.odd? # close last_tag\n        last_tag = (result[ss[1]] << code.size + 1)\n      when ss.scan(%r|«/\\w*»|)\n        last_tag << code.size + 1\n      when ss.scan(/[^«]+/), ss.scan(/«[^@\\w]/)\n        code << ss[0]\n      when ss.eos?\n        last_tag << code.size + 1 if last_tag && last_tag.size.odd? # close last_tag\n        break\n      else\n        raise \"Failed at #{ss.charpos}\"\n      end\n    end\n\n    result = result.sort_by { |k, v| k.to_i }.map { |k, v|\n      \"(%d %s)\" % [k, v.join(\" \")] if v\n    }\n\n    sexp = \"((%s %s %s %s)%s)\" % [code.size,\n                                  1,\n                                  code.size + 1,\n                                  indent_stack.join(' '),\n                                  result.join]\n    [sexp, code]\n  end\n\n  # Parses the return value of ErmBuffer::Parser#parse into Array\n  def parse_sexp(str)\n    paren_stack = []\n    result = []\n\n    ss = StringScanner.new(str)\n    until ss.eos?\n      case\n      when ss.scan(/\\(/)\n        if paren_stack.empty?\n          paren_stack.push(result)\n        else\n          paren_stack.push([])\n        end\n      when ss.scan(/\\)/)\n        if paren_stack != [result]\n          result.push(paren_stack.pop)\n        end\n      when ss.scan(/\\s+/)\n        # skip spaces\n      when ss.scan(/([^\\s()]+)/)\n        paren_stack.last.push(ss[1])\n      else\n        raise \"Failed at #{ss.charpos}.\"\n      end\n    end\n\n    result\n  end\n\n  def markup(code, parsed_sexp, options = {})\n    options = {\n      :indent    => true,\n      :highlight => true,\n      :close_tag  => false,\n      :verbose => false,\n    }.merge(options)\n\n    indents, highlights = parsed_sexp[0][3..-1], parsed_sexp[1..-1]\n    tags = [] # [[\"«tag»\", insert_position], ... ]\n\n    if options[:indent]\n      indents.each_slice(2).each do |symbol, index|\n        tags << [\"«@#{symbol}»\", index.to_i]\n      end\n    end\n\n    if options[:highlight]\n      highlights.map do |(id, *ranges)|\n        ranges.each_slice(2) do |open, close|\n          tags << [\"«#{id}»\", open.to_i]\n          tags << [\"«/#{id}»\", close.to_i] if options[:close_tag]\n        end\n      end\n    end\n\n    faces = # maps to enh-ruby-font-names\n      %w[\n         »\n         «STRING\n         «TYPE\n         «VAR\n         «COMMENT\n         «CONSTANT\n         «STRING\n         «STRINGQ\n         «REGEXPQ\n         «FUNCTION\n         «KW\n         «HEREDOC\n         «OP\n         «REGEXP\n        ]\n\n    tags.map! { |tag, index|\n      tag = faces[$1.to_i] if tag =~ /«(\\d+)»/\n      [tag, index]\n    } if options[:verbose]\n\n    indent_tags = {\n      \"l\" => \"OPEN\",     # - [, (, {, %w/%i open  or | goalpost open\n      \"r\" => \"CLOSE\",    # - ], ), }, %w/%i close or | goalpost close\n      \"b\" => \"BEGIN\",    # - begin/def/case/if\n      \"e\" => \"END\",      # - end / embexpr (interpolation) end / close block }\n      \"d\" => \"DO\",       # - do / {\n      \"s\" => \"STMT\",     # - statement start on BACKDENT_KW else/when/rescue etc\n      \"c\" => \"CONTINUE\", # - continue - period followed by return\n    }\n\n    tags.map! { |tag, index|\n      tag = \"«@#{indent_tags[$1]}»\" if tag =~ /«@(.)»/\n      [tag, index]\n    } if options[:verbose]\n\n    markup = code.dup\n    offset = 0\n    tags.sort_by { |(tag, index)|\n      # Sort tags like «/close»«@indent»«open» for human-readability.\n      # tags: 0 = close, 1 = indent, 2 = open\n      type = tag.match?(%r%^.@%) ? 0 : tag.match?(%r%^./%) ? 1 : 2\n\n      [index, type]\n    }.each do |(tag, index)|\n      markup.insert(index - 1 + offset, tag)\n      offset += tag.size\n    end\n\n    markup\n  end\nend\n"
  },
  {
    "path": "test/test_erm_buffer.rb",
    "content": "# -*- coding: utf-8 -*-\n\ngem \"minitest\"\nrequire \"minitest/autorun\"\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))\n\nrequire 'ruby/erm_buffer.rb'\nrequire 'test/helper'\n\nclass TestErmBuffer < Minitest::Test\n  def parse_text(text,buf=ErmBuffer.new)\n    buf.add_content(:r,1,text.size,0,text.size,text)\n    buf.parse\n  end\n\n  def setup\n    super\n    ErmBuffer.set_extra_keywords({})\n  end\n\n  def test_continations\n    assert_parse(%q{\n«0»\na,\n«@c»b\n})\n  end\n\n  def test_symbols\n    assert_parse('«5»:aaa')\n    assert_parse('«5»:@aa')\n    assert_parse('«5»:@@a')\n    assert_parse('«5»:$aa')\n    assert_parse('«5»:<=>')\n    assert_parse('«5»:aa')\n    assert_parse('«5»:==')\n    assert_parse('«5»:a')\n    assert_parse('«5»:+')\n    assert_parse('«5»:=')\n  end\n\n  def test_extra_keywords\n    ErmBuffer.set_extra_keywords(%w[require])\n    assert_parse(%q{\n«10»require«0» «7»'«1»abc«7»'«0»\nx.require z\nx.\n«@c»«10»require\n})\n  end\n\n  def test_buffer_local_extra_keywords\n    ErmBuffer.set_extra_keywords(%w[global])\n    local_buf=ErmBuffer.new\n    local_buf.set_extra_keywords(%w[local])\n    assert_parse(%q{«0»global «10»local}, local_buf)\n  end\n\n  def test_reset_mode\n   assert_parse(%q{\n«0»a«12»=«11»<<END«0»\n«3»#«@d»{«0»\n  «5»:x«0» «12»==«0» d\n«@e»«3»}«1»\n«11»END\n})\n  end\n\n  def test_heredoc_multi\n    assert_parse(%q{\n«0»a«12»=«11»<<E«0»,«11»<<D«0»\n«1»e\n«11»E\n«1»d\n«11»D\n})\n  end\n\n  def test_heredoc_nesting\n    assert_parse(%q{\n«0»a«12»=«11»<<E«0»\n«1»a\n«3»#«@d»{«11»<<EE«0»\n«11»EE\n«@e»«3»}«1»\n«11»E\n})\n  end\n\n  def test_heredoc_nesting_and_quoting\n    assert_parse(%q{\n«0»a«12»=«0» «11»<<E«0»,«11»<<-'D'«0»,«5»:b«0»\n«1»se«3»#«@d»{«0»\n«11»<<E«0»\n«1»inner E\n«11»E\n«@e»«3»}«1»ee\n«11»E\n«1»sd#{md}ed\n«11»   D\n«0»\np a\n})\n  end\n\n  def test_class\n    assert_parse(%q{\n«@b»«10»class«0» «2»Abc«0»\n«@e»«10»end\n})\n  end\n\n  def test_pct_w\n    skip \"TODO: broken by #587f48f\"\n    assert_parse %q{«0» «@l»«7»%w[«0» «1»a«0» «1»b«0» «1»c«0» «@r»«7»]«0» }\n  end\n\n  def test_pct_q\n    assert_parse %q{«0» «7»%q[«1» a b c «7»]}\n    assert_parse %q{«0» «7»%q[«1» a b c «7»]«0» }\n  end\n\n  def test_def\n    assert_parse(%q{\n«@b»«10»def«0» «9»simple«@l»«0»(a,b«@r»)\n  «5»:if«0»\n«@e»«10»end«0»\n\n«@b»«10»def«0» «2»Const«0».«9»method«0»\n  ident\n«@e»«10»end«0»\n\n«@b»«10»def«0» «10»self«0».«9»m«@l»«0»(a,\n           «12»&«0»block«@r»)\n  body\n«@e»«10»end«0»\n\n«@b»«10»def«0» «@l»(a «12»+«0» b «@d»«10»{«@l»«3»|«0»a«@r»«3»|«0»\n  «@b»«10»def«0» «9»a«0»\n  «@e»«10»end«0»\n«@e»«10»}«@r»«0»).«9»d«@l»«0»(a«@r»)\n  body\n«@s»«10»rescue«0» «2»Exception«0» «12»=>«0» e\n  raise\n«@e»«10»end\n})\n  end\n\n  def test_endless_def\n    assert_parse(%q{\n«@b»«10»def«0» «10»self«0».«9»m«0» «@e»«12»=«0» ident\n\n«@b»«10»def«0» «9»m«0» «@e»«12»=«0» ident\n\n«@b»«10»def«0» «9»m«@l»«0»(a«@r») «@e»«12»=«0» ident\n\n«@b»«10»def«0» «9»m«@l»«0»(«12»*«0»a, «12»**«0»k, «12»&«0»b«@r») «@e»«12»=«0» ident\n\n«@b»«10»def«0» «9»m«@l»«0»(«12»...«@r»«0») «@e»«12»=«0» ident\n\n«@b»«10»def«0» «9»m«0» «@e»«12»=«0»\n«@c»  ident\n})\n  end\n\n  def test_heredoc_followed_by_if_arg\n    assert_parse(%q{\n«0»bob«@l»(«11»<<-END«0», «@b»«10»if«0» a\n«1»fdssdfdsf\n            dfsdfs\"\n«11»END\n«0»\n              fds\n            «@s»«10»elsif«0» b\n              fds\n            «@e»«10»end«0»\n«@r»)\n\na «10»if«0» b\nc\n\na«@l»(sdfdsf«@l»(«@r»),\n  «@b»«10»if«0» fds\n    dfs\n  «@e»«10»end«0»\n  «@r»)\n})\n  end\n\n  def test_if\n    assert_parse(%q{\n«0»a «10»if«0» dsf\n«@b»«10»if«0» a\n  b\n«@s»«10»else«0»\n  c\n«@e»«10»end«0»\n\nb«12»=«0»d «10»if«0» sdf\n\na«10»;«0» «@b»«10»if«0» b\n     c\n   «@e»«10»end«0»\n\na«@l»(b,«@b»«10»if«0» c\n      d\n    «@e»«10»end«0»\n  «@r»)\n\na«@l»(«@b»«10»if«0» c\n    d\n  «@e»«10»end«0»\n  «@r»)\n\na«12»=«@l»«0»{«5»a:«0» fds,\n   «5»:b«0» «12»=>«0» fds\n   «@r»}\n})\n  end\n\n  def test_utf8_here_docs\n    assert_parse(%q{\n«3»@ü«12»=«11»<<E«0»\n«1»á\n«11»E\n})\n  end\n\n  def test_interpolated_string\n    assert_parse(%q{«7»\"«3»#«@d»{«0»1«@e»«3»}«7»\"})\n\n    assert_parse(%q{\n«0»puts «7»\"«3»#«@d»{«0»\n  «@l»[1, 2, 3«@r»].map «@d»«10»do«0» «@l»«3»|«0»i«@r»«3»|«0»\n    p «7»\"«3»#«@d»{«0»\n      i«12»*«0»i\n    «@e»«3»}«7»\"«0»\n  «@e»«10»end«0»\n«@e»«3»}«7»\"\n})\n  end\n\n  def test_keyword_do_block\n    assert_parse(%q{\n«0»each «@d»«10»do«0»\n«@e»«10»end\n})\n  end\n\n  def test_keyword_do_cond\n    assert_parse(%q{\n«@b»«10»while«0» «10»false«0» «10»do«0»\n«@e»«10»end\n})\n\n    assert_parse(%q{\n«@b»«10»while«0» «@l»(«@b»«10»until«0» «10»false«0» «10»do«0»\n       «@e»«10»end«@r»«0») «10»do«0»\n«@e»«10»end\n})\n  end\n\n  def test_percent_literals_w\n    skip \"TODO: broken by #587f48f\"\n    assert_parse(%q{\n«@l»«0»[\n  «7»\"«1»one«7»\"«0»\n«@r»]\n«@l»[«7»\"«1»one«7»\"«@r»«0»]\n\n«@l»«7»%w(«0»\n  «1»one«0»\n«@r»«7»)«0»\n«@l»«7»%W(«0» «1»one«0» «3»#«@d»{«5»:two«@e»«3»}«0» «@r»«7»)«0»\n\n«@b»«10»begin«0»\n  a «12»=«0» «@l»[\n    «7»\"«1»one«7»\"«0»\n  «@r»]\n  a «12»=«0» «@l»«7»%w[«0»\n    «1»one«0»\n  «@r»«7»]«0»\n«@e»«10»end\n})\n  end\n\n  def test_percent_literals_i\n    skip \"TODO: broken by #587f48f\"\n    # %i didn't exist before ruby 2.0\n    return if RUBY_VERSION.split(\".\").first.to_i < 2\n\n    assert_parse(%q{\n«@l»«7»%i[«1»one«0» «@r»«7»]«0»\n\n«@l»«7»%I'«1»a«0» «1»\\#{a}«@r»«7»'«0»\n«@l»«7»%I'«1»a«0» «3»#«@d»{«0»a«@e»«3»}«@r»«7»'\n})\n  end\n\n  def test_dot_indent\n    assert_parse(%q{\n«0»a.b\n\nc.\n«@c»  d\n\ng\n«@c»  .h\n\ni.j\n«@c»  .h\n})\n  end\n\n  def test_dot_indent_with_block\n    assert_parse(%q{\n«0»a\n«@c»  .b «@d»«10»{«0» «@e»«10»}«0»\n«@c»  .c\n})\n  end\n\n  def test_dot_indent_with_comment\n    assert_parse(%q{\n«0»a\n«@c»  .b «4»# comment\n«@c»«0»  .c\n})\n  end\n\n  def test_brace_after_identifer\n    assert_parse(%q[\n«0»a\n«@l»{\n«5»:b«0» «12»=>«0» c,\n«@r»}\n])\n  end\nend\n"
  },
  {
    "path": "tools/debug.rb",
    "content": "#!/usr/bin/env ruby -w\n\nrequire_relative '../ruby/erm_buffer'\n\ntrace = ARGV.delete \"--trace\"\ndebug = ARGV.delete \"-d\"\n\nclass ErmBuffer::Parser\n  alias :old_realadd :realadd\n  def realadd(sym,tok,len)\n    x = old_realadd(sym, tok, len)\n    k = sym =~ /^rem_/ ? :rem : sym\n    v = ErmBuffer::FONT_LOCK_NAMES[k] || -1\n    puts \"%2d %-20p %3d %p\" % [v, sym, len, tok]\n    x\n  end\nend\n\nif trace then\n  require \"tracer\"\n  Tracer.on\nend\n\nARGV.each do |file|\n  buf = ErmBuffer.new\n  buf.debug = true if debug\n  content = File.read file\n  point_min, point_max, pbeg, len = 1, content.size+1, 0, content.size\n\n  buf.add_content :x, point_min, point_max, pbeg, len, content\n\n  puts buf.parse\nend\n"
  },
  {
    "path": "tools/lexer.rb",
    "content": "#!/usr/bin/env ruby -w\n\nrequire \"ripper\"\nrequire \"pp\"\n\nARGV.each do |p|\n  f = File.read p\n  puts\n  pp Ripper.lex f\n  puts\n  pp Ripper.sexp_raw f\n  puts\n  pp Ripper.sexp f\nend\n"
  },
  {
    "path": "tools/markup.rb",
    "content": "#!/usr/bin/env ruby -w\n\nrequire_relative '../test/markup'\n\nif ARGV.delete(\"--help\")\n  puts <<-HERE\nUsage: ruby #{__FILE__} <programfile> <options>\n\nOptions:\n  --help\n  --no-indent\n  --no-highlight\n  -v\n    HERE\n\n  exit\nend\n\noptions = {\n  indent: !ARGV.delete(\"--no-indent\"),\n  highlight: !ARGV.delete(\"--no-highlight\"),\n  verbose: ARGV.delete(\"-v\"),\n}\n\nsrc = ARGF.read\nsexp = Markup.parse_code(src)\nmarkup = Markup.markup(src, Markup.parse_sexp(sexp), options)\n\nputs sexp\nputs \"---\"\nputs markup\n"
  }
]