Table of Contents

1. Ruby Development Configuration

Configuration for Ruby and Ruby on Rails development.

1.1. Overview

This module provides comprehensive Ruby development support:

  • Language Support: ruby-mode with enhanced features
  • Rails Integration: Full Ruby on Rails support
  • REPL: inf-ruby for interactive development
  • Testing: RSpec and minitest integration
  • Code Quality: RuboCop integration
  • Package Management: Bundler support

1.2. Key Features

1.2.1. Ruby Mode

  • Syntax highlighting
  • Intelligent indentation
  • Code navigation
  • Symbol navigation

1.2.2. Ruby on Rails

  • Rails-specific features
  • Generator integration
  • Migration helpers
  • View/controller switching

1.2.3. Development Tools

Tool Purpose
ruby-mode Core Ruby language support
inf-ruby Interactive Ruby REPL
rspec-mode RSpec test runner
rubocop Ruby style checker
bundler Gem dependency management

1.2.4. Workflow Integration

  1. Project detection (Rails vs. pure Ruby)
  2. Bundler gem management
  3. Test runner integration
  4. Code formatting and linting

1.3. Ruby

;;; prog-ruby-conf.el --- Ruby development configuration -*- lexical-binding: t; -*-
;;; Commentary:
;;; Configuration for Ruby and Ruby on Rails development including LSP support,
;;; testing, formatting, REPL, and Rails-specific tools.
;;; Code:

;; Ruby mode settings
(use-package ruby-mode
  :ensure nil
  :mode (("\\.rb\\'" . ruby-mode)
         ("Rakefile\\'" . ruby-mode)
         ("Gemfile\\'" . ruby-mode)
         ("Berksfile\\'" . ruby-mode)
         ("Vagrantfile\\'" . ruby-mode)
         ("\\.rake\\'" . ruby-mode)
         ("\\.gemspec\\'" . ruby-mode)
         ("\\.ru\\'" . ruby-mode)
         ("Guardfile\\'" . ruby-mode)
         ("Capfile\\'" . ruby-mode)
         ("\\.cap\\'" . ruby-mode)
         ("\\.thor\\'" . ruby-mode)
         ("\\.rabl\\'" . ruby-mode)
         ("Thorfile\\'" . ruby-mode)
         ("\\.jbuilder\\'" . ruby-mode)
         ("Podfile\\'" . ruby-mode)
         ("\\.podspec\\'" . ruby-mode)
         ("Puppetfile\\'" . ruby-mode)
         ("\\.arb\\'" . ruby-mode))
  :interpreter "ruby"
  :custom
  (ruby-indent-level 2)
  (ruby-indent-tabs-mode nil)
  (ruby-insert-encoding-magic-comment nil)  ; Don't auto-insert encoding comment
  (ruby-align-chained-calls nil)
  (ruby-deep-indent-paren nil)
  :config
  ;; Remove trailing whitespace on save
  (add-hook 'ruby-mode-hook
            (lambda ()
              (add-hook 'before-save-hook #'delete-trailing-whitespace nil t)))

  ;; Enhance Ruby mode with additional features
  (add-hook 'ruby-mode-hook
            (lambda ()
              (setq-local comment-auto-fill-only-comments t)
              (auto-fill-mode 1))))

;; inf-ruby: Ruby REPL integration
(use-package inf-ruby
  :after ruby-mode
  :hook ((ruby-mode . inf-ruby-minor-mode)
         (compilation-filter . inf-ruby-auto-enter))
  :bind (:map ruby-mode-map
              ("C-c C-s" . inf-ruby)
              ("C-c C-z" . inf-ruby-switch-from-compilation)
              ("C-c C-c" . fu-ruby-send-region-or-buffer)
              ("C-c C-d" . fu-ruby-send-definition)
              ("C-c C-l" . ruby-load-file)
              ("C-c C-r" . ruby-send-region))
  :config
  ;; Automatically start REPL when evaluating code
  (defun fu-ruby-send-region-or-buffer ()
    "Send the current region to Ruby REPL, or whole buffer if no region is active."
    (interactive)
    (unless (get-buffer-process inf-ruby-buffer)
      (inf-ruby))
    (if (use-region-p)
        (ruby-send-region (region-beginning) (region-end))
      (ruby-send-buffer)))

  (defun fu-ruby-send-definition ()
    "Send the current definition to Ruby REPL."
    (interactive)
    (unless (get-buffer-process inf-ruby-buffer)
      (inf-ruby))
    (save-excursion
      (ruby-end-of-defun)
      (let ((end (point)))
        (ruby-beginning-of-defun)
        (ruby-send-region (point) end)))))

;; RuboCop: Linting and formatting
(use-package rubocop
  :defer t
  :hook (ruby-mode . rubocop-mode)
  :bind (:map rubocop-mode-map
              ("C-c r a" . rubocop-autocorrect-current-file)
              ("C-c r A" . rubocop-autocorrect-project)
              ("C-c r f" . rubocop-check-current-file)
              ("C-c r p" . rubocop-check-project))
  :custom
  (rubocop-autocorrect-on-save nil)  ; Don't auto-correct on save by default
  (rubocop-prefer-system-executable t)
  :config
  ;; Auto-format with RuboCop if available
  (defun fu-ruby-rubocop-autocorrect-on-save ()
    "Auto-correct current file with RuboCop on save if rubocop is available."
    (when (and (eq major-mode 'ruby-mode)
               (executable-find "rubocop"))
      (rubocop-autocorrect-current-file)))

  ;; Uncomment to enable auto-format on save
  ;; (add-hook 'after-save-hook #'fu-ruby-rubocop-autocorrect-on-save))
  )

;; RSpec: Testing framework
(use-package rspec-mode
  :after ruby-mode
  :hook (ruby-mode . rspec-mode)
  :bind (:map ruby-mode-map
              ("C-c t t" . rspec-verify)
              ("C-c t f" . rspec-verify-single)
              ("C-c t m" . rspec-verify-matching)
              ("C-c t a" . rspec-verify-all)
              ("C-c t r" . rspec-rerun)
              ("C-c t c" . rspec-verify-continue)
              ("C-c t s" . rspec-toggle-spec-and-target))
  :custom
  (rspec-use-rake-when-possible nil)
  (rspec-spec-command "rspec")
  (rspec-use-spring-when-possible t)
  :config
  ;; Use bundle exec if Gemfile exists
  (defun fu-ruby-use-bundler-for-rspec ()
    "Use bundle exec for RSpec if Gemfile exists."
    (when (locate-dominating-file default-directory "Gemfile")
      (setq rspec-spec-command "bundle exec rspec")))

  (add-hook 'ruby-mode-hook #'fu-ruby-use-bundler-for-rspec))

;; Bundler: Dependency management
;; Note: bundler package is no longer available on MELPA
;; Using custom functions with compilation-mode instead
(defun fu-bundle-install ()
  "Run bundle install in the project root."
  (interactive)
  (let ((default-directory (or (locate-dominating-file default-directory "Gemfile")
                               default-directory)))
    (compile "bundle install")))

(defun fu-bundle-update ()
  "Run bundle update in the project root."
  (interactive)
  (let ((default-directory (or (locate-dominating-file default-directory "Gemfile")
                               default-directory)))
    (compile "bundle update")))

(defun fu-bundle-exec (command)
  "Run COMMAND via bundle exec in the project root."
  (interactive "sBundle exec: ")
  (let ((default-directory (or (locate-dominating-file default-directory "Gemfile")
                               default-directory)))
    (compile (concat "bundle exec " command))))

;; Rake: Task runner
(use-package rake
  :after ruby-mode
  :commands (rake rake-find-task rake-rerun)
  :bind (:map ruby-mode-map
              ("C-c k r" . rake)
              ("C-c k f" . rake-find-task)
              ("C-c k R" . rake-rerun)))

;; YAML mode for Rails config files
(use-package yaml-mode
  :mode (("\\.yml\\'" . yaml-mode)
         ("\\.yaml\\'" . yaml-mode)
         ("config/database.yml" . yaml-mode)
         ("config/routes.rb" . ruby-mode)))

;; Enhanced Ruby refactoring
(use-package ruby-refactor
  :defer t
  :hook (ruby-mode . ruby-refactor-mode-launch)
  :bind (:map ruby-refactor-mode-map
              ("C-c C-e m" . ruby-refactor-extract-to-method)
              ("C-c C-e v" . ruby-refactor-extract-local-variable)
              ("C-c C-e c" . ruby-refactor-extract-constant)
              ("C-c C-e l" . ruby-refactor-extract-to-let)))

;; Ruby block manipulation
;; Note: ruby-block package is no longer available on MELPA
;; show-paren-mode provides similar functionality for matching blocks

;; YARD documentation
(use-package yard-mode
  :after ruby-mode
  :hook (ruby-mode . yard-mode)
  :bind (:map ruby-mode-map
              ("C-c y" . yard-mode)))

;; Ruby debugging with realgud
(use-package realgud
  :defer t
  :commands (realgud:byebug realgud:pry))

;; Chruby for Ruby version management (if you use chruby)
(use-package chruby
  :defer t
  :commands (chruby-use chruby-use-corresponding)
  :config
  ;; Auto-detect Ruby version from .ruby-version
  (defun fu-ruby-chruby-auto ()
    "Auto-select Ruby version based on .ruby-version file."
    (when (and (executable-find "chruby-exec")
               (locate-dominating-file default-directory ".ruby-version"))
      (chruby-use-corresponding)))

  (add-hook 'ruby-mode-hook #'fu-ruby-chruby-auto))

;; RVM support (alternative to chruby)
;; (use-package rvm
;;   :defer t
;;   :config
;;   (rvm-use-default))

;; rbenv support (alternative to chruby/rvm)
;; (use-package rbenv
;;   :defer t
;;   :config
;;   (global-rbenv-mode))

;; ERB (Embedded Ruby) mode
(use-package web-mode
  :mode (("\\.erb\\'" . web-mode)
         ("\\.html\\.erb\\'" . web-mode))
  :config
  (setq web-mode-engines-alist
        '(("erb" . "\\.html\\.erb\\'"))))

;; Haml support
(use-package haml-mode
  :mode "\\.haml\\'")

;; Slim template support
(use-package slim-mode
  :mode "\\.slim\\'")

;; Rails console in Emacs
;; (defun fu-rails-console ()
;;   "Start Rails console in inf-ruby."
;;   (interactive)
;;   (let ((default-directory (or (projectile-rails-root)
;;                                default-directory)))
;;     (if (file-exists-p "bin/rails")
;;         (inf-ruby-console-run "bin/rails console" "rails")
;;       (inf-ruby-console-run "bundle exec rails console" "rails"))))

;; (defun fu-rails-server ()
;;   "Start Rails server in async shell."
;;   (interactive)
;;   (let ((default-directory (or (projectile-rails-root)
;;                                default-directory)))
;;     (async-shell-command
;;      (if (file-exists-p "bin/rails")
;;          "bin/rails server"
;;        "bundle exec rails server")
;;      "*rails-server*")))

;; Keybindings for Rails
;; (with-eval-after-load 'ruby-mode
;;   (define-key ruby-mode-map (kbd "C-c R r") 'fu-rails-console)
;;   (define-key ruby-mode-map (kbd "C-c R s") 'fu-rails-server))

;; Ruby hash syntax conversion helpers
(defun fu-ruby-toggle-hash-syntax ()
  "Toggle between old and new Ruby hash syntax."
  (interactive)
  (save-excursion
    (let ((bounds (bounds-of-thing-at-point 'symbol)))
      (when bounds
        (let* ((start (car bounds))
               (end (cdr bounds))
               (symbol (buffer-substring-no-properties start end)))
          (cond
           ;; Convert :symbol => to symbol:
           ((looking-back ":\\([a-z_][a-z0-9_]*\\) =>" (line-beginning-position))
            (let ((match-start (match-beginning 0)))
              (delete-region match-start (point))
              (insert (match-string 1) ":")))
           ;; Convert symbol: to :symbol =>
           ((looking-at "\\([a-z_][a-z0-9_]*\\):")
            (let ((symbol (match-string 1)))
              (delete-region (match-beginning 0) (match-end 0))
              (insert ":" symbol " =>")))))))))

(with-eval-after-load 'ruby-mode
  (define-key ruby-mode-map (kbd "C-c h") 'fu-ruby-toggle-hash-syntax))

;; LSP configuration with Eglot
(with-eval-after-load 'eglot
  ;; Configure ruby-lsp server (preferred) or solargraph
  (add-to-list 'eglot-server-programs '((ruby-mode ruby-ts-mode) "ruby-lsp"))

  ;; Alternative: Use solargraph instead
  ;; (add-to-list 'eglot-server-programs
  ;;              '((ruby-mode ruby-ts-mode) "solargraph" "stdio"))

  ;; Enable eglot for Ruby modes
  (add-hook 'ruby-mode-hook #'eglot-ensure)
  (add-hook 'ruby-ts-mode-hook #'eglot-ensure))

;; Ruby end-of-line comments alignment
(defun fu-ruby-align-end-comments ()
  "Align end-of-line comments in Ruby."
  (interactive)
  (align-regexp (region-beginning) (region-end) "\\(\\s-*\\)#" 1 1 t))

(provide 'prog-ruby-conf)
;;; prog-ruby-conf.el ends here