Ruby 3 IRB home end and ctrl-u not working - ruby

I've recently started using Ruby 3 more and it seems the home key (to take me to the beginning of the line) the end key (end of the line) and ctrl-u (clear the line) aren't working.
I'm running Arch Linux with Ruby 3.1.1p18 and a zsh shell. The keys work fine on Ruby 2.7. I've tried with Alacritty and xfce4-terminal and both have the same issue. I'm not using Tmux or anything similar.
There is this similar question from a few years ago but that's for Windows and the solutions didn't help: Backspace and arrow keys aren't working in IRB(Git Bash console) on windows machine

I also specifically see ctrl-u not working in Ruby 3's irb, despite other ctrl commands working (ctrl-a, ctrl-k).
For the meantime, I'm using pry for my REPL. Bonus… I prefer it's simpler auto-complete.
gem install pry
pry

I can't take credit for this as this is just a copy from: https://github.com/ruby/irb/issues/330, but I hope this is useful for those that stumbled upon this question without a good answer.
For those who would prefer to do the above patches at runtime, for macOS/iTerm2, add this code to "~/.irbrc":
require "reline/ansi"
if defined?(Reline::ANSI::CAPNAME_KEY_BINDINGS) # make sure you're using an affected version
# Fix insert, delete, pgup, and pgdown.
Reline::ANSI::CAPNAME_KEY_BINDINGS.merge!({
"kich1" => :ed_ignore,
"kdch1" => :key_delete,
"kpp" => :ed_ignore,
"knp" => :ed_ignore
})
Reline::ANSI.singleton_class.prepend(
Module.new do
def set_default_key_bindings(config)
# Fix home and end.
set_default_key_bindings_comprehensive_list(config)
# Fix iTerm2 insert.
key = [239, 157, 134]
func = :ed_ignore
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
# The rest of the behavior.
super
end
end
)
end
No switch to application mode needed, and no iTerm2 rebinding.

Related

Ruby spawn process, capturing STDOUT/STDERR, while behaving as if it were spawned regularly

What I'm trying to achieve:
From a Ruby process, spawning a subprocess
The subprocess should print as normal back to the terminal. By "normal", I mean the process shouldn't miss out color output, or ignore user input (STDIN).
For that subprocess, capturing STDOUT/STDERR (jointly) e.g. into a String variable that can be accessed after the subprocess is dead. Escape characters and all.
Capturing STDOUT/STDERR is possible by passing a different IO pipe, however the subprocess can then detect that it's not in a tty. For example git log will not print characters that influence text color, nor use it's pager.
Using a pty to launch the process essentially "tricks" the subprocess into thinking it's being launched by a user. As far as I can tell, this is exactly what I want, and the result of this essentially ticks all the boxes.
My general tests to test if a solution fits my needs is:
Does it run ls -al normally?
Does it run vim normally?
Does it run irb normally?
The following Ruby code is able to check all the above:
to_execute = "vim"
output = ""
require 'pty'
require 'io/console'
master, slave = PTY.open
slave.raw!
pid = ::Process.spawn(to_execute, :in => STDIN, [:out, :err] => slave)
slave.close
master.winsize = $stdout.winsize
Signal.trap(:WINCH) { master.winsize = $stdout.winsize }
Signal.trap(:SIGINT) { ::Process.kill("INT", pid) }
master.each_char do |char|
STDOUT.print char
output.concat(char)
end
::Process.wait(pid)
master.close
This works for the most part but it turns out it's not perfect. For some reason, certain applications seem to fail to switch into a raw state. Even though vim works perfectly fine, it turned out neovim did not. At first I thought it was a bug in neovim but I have since been able to reproduce the problem using the Termion crate for the Rust language.
By setting to raw manually (IO.console.raw!) before executing, applications like neovim behave as expected, but then applications like irb do not.
Oddly spawning another pty in Python, within this pty, allows the application to work as expected (using python -c 'import pty; pty.spawn("/usr/local/bin/nvim")'). This obviously isn't a real solution, but interesting nonetheless.
For my actual question I guess I'm looking towards any help to resolving the weird raw issue or, say if I've completely misunderstood tty/pty, any different direction to where/how I should look at the problem.
[edited: see the bottom for the amended update]
Figured it out :)
To really understand the problem I read up a lot on how a PTY works. I don't think I really understood it properly until I drew it out. Basically PTY could be used for a Terminal emulator, and that was the simplest way to think of the data flow for it:
keyboard -> OS -> terminal -> master pty -> termios -> slave pty -> shell
|
v
monitor <- OS <- terminal <- master pty <- termios
(note: this might not be 100% correct, I'm definitely no expert on the subject, just posting it incase it helps anybody else understand it)
So the important bit in the diagram that I hadn't really realised was that when you type, the only reason you see your input on screen is because it's passed back (left-wards) to the master.
So first thing's first - this ruby script should first set the tty to raw (IO.console.raw!), it can restore it after execution is finished (IO.console.cooked!). This'll make sure the keyboard inputs aren't printed by this parent Ruby script.
Second thing is the slave itself should not be raw, so the slave.raw! call is removed. To explain this, I originally added this because it removes extra return carriages from the output: running echo hello results in "hello\r\n". What I missed was that this return carriage is a key instruction to the terminal emulator (whoops).
Third thing, the process should only be talking to the slave. Passing STDIN felt convenient, but it upsets the flow shown in the diagram.
This brings up a new problem on how to pass user input through, so I tried this. So we basically pass STDIN to the master:
input_thread = Thread.new do
STDIN.each_char do |char|
master.putc(char) rescue nil
end
end
that kind of worked, but it has its own issues in terms of some interactive processes weren't receiving a key some of the time. Time will tell, but using IO.copy_stream instead appears to solve that issue (and reads much nicer of course).
input_thread = Thread.new { IO.copy_stream(STDIN, master) }
update 21st Aug:
So the above example mostly worked, but for some reason keys like CTRL+c still wouldn't behave correctly. I even looked up other people's approach to see what I could be doing wrong, and effectively it seemed the same approach - as IO.copy_stream(STDIN, master) was successfully sending 3 to the master. None of the following seemed to help at all:
master.putc 3
master.putc "\x03"
master.putc "\003"
Before I went and delved into trying to achieve this in a lower level language I tried out 1 more thing - the block syntax. Apparently the block syntax magically fixes this problem.
To prevent this answer getting a bit too verbose, the following appears to work:
require 'pty'
require 'io/console'
def run
output = ""
IO.console.raw!
input_thread = nil
PTY.spawn('bash') do |read, write, pid|
Signal.trap(:WINCH) { write.winsize = STDOUT.winsize }
input_thread = Thread.new { IO.copy_stream(STDIN, write) }
read.each_char do |char|
STDOUT.print char
output.concat(char)
end
Process.wait(pid)
end
input_thread.kill if input_thread
IO.console.cooked!
end
Bundler.send(:with_env, Bundler.clean_env) do
run
end

puts line breaking in_branch in git gem?

I'm automating a bit of git workflow and have found some curious behaviour when using the git gem's in_branch method and wondered if anyone could explain why or how this issue occurs? Here's some test code that should reproduce the issue:
#!/usr/bin/env ruby
require 'git'
# git details
repo_name = 'SOME_REPO'
working_dir = "#SOME_DIR/git_test_repo"
repo_owner = 'SOME_GIT_USER'
repo_host = 'SOME_GIT_HOST'
repo_dir = "#{working_dir}/#{repo_name}"
remote_repo = "git##{repo_host}:#{repo_owner}/#{repo_name}.git"
branch_name = 'testbranch'
commit_message = 'log line breaking in_branch test'
Dir.mkdir(working_dir) unless Dir.exist?(working_dir)
Dir.chdir(working_dir)
Git.clone(remote_repo, repo_name) unless Dir.exists?(repo_dir)
repo = Git.open(repo_dir)
repo.pull(remote = 'origin', branch = 'master')
repo.branch(branch_name).in_branch(message = commit_message) do
File.write(repo_dir + '/test.txt', Time.now)
repo.add('.')
# -----this line breaks it --------------
puts 'committing changes'
# ---------------------------------------
end
When this code runs, the last puts line before the end of the in_branch block, when actually run, somehow causes the changes in the branch to be reverted, but when it's commented out, all the code behaves as expected. I've tested output lines anywhere in the block, and they all behave fine. It seems to happen across many versions of ruby (custom installs, rvm installs) and different OS's (linuxes and mac).
Is there some arcane behaviour of ruby and its terminal output I need to be aware of here?
Not really "arcane behavior of ruby". But the result of the last line in a method is what the method returns. Putting a puts in that position will usually break the method.
Just testing and I see the same behavior using print (which does not add a newline).
This is not a newline issue, moving the (now print) line above the repo.add line and all works.
Anymore thoughts?

Vim option value contains error message when editing ruby files, Gem.all_load_paths

When editing ruby, some files but not all are garbled when editing in vim.
Inspecting the options with :set, I discovered that there is some magic done to produce one of the option values, and something went wrong with the magic and there is an error or warning message where the option value should be. That may be causing side effects.
The method throwing the error is Gem.all_load_paths, and it happens regardless of whether I use ruby 1.8.7, 1.9.2, whether I use rvm or system ruby. Using Ubuntu 11.10
I've tried setting the omnifunc option to nil, but that doesn't solve the problem; it seems to be a different option producing this value. The plugins I'm using can be seen below.
:set
--- Options ---
autoindent comments=:# history=50 keywordprg=ri scroll=29 suffixesadd=.rb ttyfast
backup filetype=ruby hlsearch mouse=a shiftwidth=2 syntax=ruby ttymouse=xterm2
backupdir=~/.tmp helplang=en incsearch ruler showcmd tabstop=2
backspace=indent,eol,start
balloonexpr=RubyBalloonexpr()
commentstring=# %s
fileencodings=ucs-bom,utf-8,default,latin1
formatoptions=croql
include=^\s*\<\(load\|w*require\)\>
includeexpr=substitute(substitute(v:fname,'::','/','g'),'$','.rb','')
indentexpr=GetRubyIndent()
indentkeys=0{,0},0),0],!^F,o,O,e,=end,=elsif,=when,=ensure,=rescue,==begin,==end
omnifunc=rubycomplete#Complete f
rom ~/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/site_ruby/1.8/rubygems/deprecate.rb:62:in `all_load_paths'^#^Ifrom -e:1^#1.8/rubygems/deprecate.rb:62:in `send'^#^I
printoptions=paper:letter /
after,/var/lib/vim/addons/after,~/.vim/afterm,~/.vim/bundle/vim-rails,/var/lib/vim/addons,/usr/share/vim/vimfiles,/usr/share/vim/vim73,/usr/share/vim/vimfiles
suffixes=.bak,~,.swp,.o,.info,.aux,.log,.dvi,.bbl,.blg,.brf,.cb,.ind,.idx,.ilg,.inx,.out,.toc
I got the same thing today after updating from Snapshot 63 to Snapshot 64 of MacVim. It's an ugly hack, but I found the offending line (79) in MacVim.app/Contents/Resources/vim/runtime/ftplugin/ruby.vim and removed the reference to Gem.all_load_paths, which is deprecated with no replacement.
I've posted a diff of my gist that gets me going. It's not clear to me at this point if this is a bug to be reported against MacVim or Vim.
I built on #pbyme a little bit and actually found the new way to do it. With my current setup, it's even pulling in some local gems brought in by bundler.
The key is replacing all_load_paths with Specification.map(&:lib_dirs_glob)
MacVim.app/Contents/Resources/vim/runtime/ftplugin/ruby.vim
79c79
< let s:code = "print ($: + begin; require %q{rubygems}; Gem.all_load_paths.sort.uniq; rescue LoadError; []; end).join(%q{,})"
---
> let s:code = "print ($: + begin; require %q{rubygems}; Gem::Specification.map(&:lib_dirs_glob).sort.uniq; rescue LoadError; []; end).join(%q{,})"

Unicode filenames on Windows in Ruby

I have a piece of code that looks like this:
Dir.new(path).each do |entry|
puts entry
end
The problem comes when I have a file named こんにちは世界.txt in the directory that I list.
On a Windows 7 machine I get the output:
???????.txt
From googling around, properly reading this filename on windows seems to be an impossible task. Any suggestions?
I had the same problem & just figured out how to get the entries of a directory in UTF-8 in Windows. The following worked for me (using Ruby 1.9.2p136):
opts = {}
opts[:encoding] = "UTF-8"
entries = Dir.entries(path, opts)
entries.each do |entry|
# example
stat = File::stat(entry)
puts "Size: " + String(stat.size)
end
You're out of luck with pure ruby (either 1.8 or 1.9.1) since it uses the ANSI versions of the Windows API.
It seems like Ruby 1.9.2 will support Unicode filenames on Windows. This bug report has 1.9.2 as target. According to this announcement Ruby 1.9.2 will be released at the end of July 2010.
If you really need it earlier you could try to use FindFirstFileW etc. directly via Win32API.new or win32-api.
My solution was to use Dir.glob instead of Dir.entries. But it only works with * parameter. It does not work when passing a path (c:/dir/*). Tested in 1.9.2p290 and 1.9.3p0 on Windows 7.
There are many other issues with unicode paths on Windows. It is still an open issue. The patches are currently targeted at Ruby 2.0, which is rumored to be released in 2013.

How do I drop to the IRB prompt from a running script?

Can I drop to an IRB prompt from a running Ruby script?
I want to run a script, but then have it give me an IRB prompt at a point in the program with the current state of the program, but not just by running rdebug and having a breakpoint.
Pry (an IRB alternative) also lets you do this, in fact it was designed from the ground up for exactly this use case :)
It's as easy as putting binding.pry at the point you want to start the session:
require 'pry'
x = 10
binding.pry
And inside the session:
pry(main)> puts x
=> 10
Check out the website: http://pry.github.com
Pry let's you:
drop into a session at any point in your code
view method source code
view method documentation (not using RI so you dont have to pre-generate it)
pop in and out of different contexts
syntax highlighting
gist integration
view and replay history
open editors to edit methods using edit obj.my_method syntax
A tonne more great and original features
you can use ruby-debug to get access to irb
require 'rubygems'
require 'ruby-debug'
x = 23
puts "welcome"
debugger
puts "end"
when program reaches debugger you will get access to irb.
apparently it requires a chunk of code to drop into irb.
Here's the link (seems to work well).
http://jameskilton.com/2009/04/02/embedding-irb-into-your-ruby-application
require 'irb'
module IRB
def self.start_session(binding) # call this method to drop into irb
unless #__initialized
args = ARGV
ARGV.replace(ARGV.dup)
IRB.setup(nil)
ARGV.replace(args)
#__initialized = true
end
workspace = WorkSpace.new(binding)
irb = Irb.new(workspace)
#CONF[:IRB_RC].call(irb.context) if #CONF[:IRB_RC]
#CONF[:MAIN_CONTEXT] = irb.context
catch(:IRB_EXIT) do
irb.eval_input
end
end
end
This feature is available from Ruby 2.4. You can just use binding.irb
E.g.
require 'irb'
a = 10
binding.irb
puts a
If you run above code, you will get irb console, so that you can inspect values of local variables and anything else that is in scope.
Source: http://blog.redpanthers.co/new-binding-irb-introduced-ruby-2-4/
Ruby commit: https://github.com/ruby/ruby/commit/493e48897421d176a8faf0f0820323d79ecdf94a
Just add this line to where you want the breakpoint:
require 'ruby-debug';debugger
but i suggest use pry instead of irb, which is super handy, insert the following line instead:
require 'pry'; binding.pry
I'm quite late to the game but if you're loading a script from within irb/pry already, a simple raise also works to pop you back out to the irb/pry prompt. I use this quite often when writing one off scripts within the rails console.

Resources