Skip to content

Highlight the method name in method calls#1189

Open
shugo wants to merge 11 commits intoruby:masterfrom
shugo:feature/irb-highlight-method-calls
Open

Highlight the method name in method calls#1189
shugo wants to merge 11 commits intoruby:masterfrom
shugo:feature/irb-highlight-method-calls

Conversation

@shugo
Copy link
Copy Markdown
Member

@shugo shugo commented Mar 25, 2026

It's useful to differentiate method calls from local variable references.

shugo added 3 commits March 25, 2026 11:34
It's useful to differentiate method calls from local variable references.
Method calls in `binding.irb` may be colorized.
This fix prevents struct members from being colorized.
super
end

def visit_call_node(node)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writer methods are also colored blue, but Writer methods with operators are not colored.

# "foo" is colored
a.foo = 1
# "foo" is not colored
a.foo += 1
a.foo &&= 1
a.foo ||= 1

So I think we can also add visitor methods for CallAndWriteNode CallOperatorWriteNode CallOrWriteNode.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it in 66933a9.


def visit_call_node(node)
if @colorize_call
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a[:key] a[:key] = 2, message_loc contains index part and wrongly colored blue, so we need to exclude :[] and :[]= when call_operator_loc is nil.

When call_operator_loc is present(a.[]=(1, 2)) message_loc is already colored correctly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it in 8643c13.

if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
# Operators should not be highlighted
else
dispatch node.message_loc, :message_name
Copy link
Copy Markdown
Member

@tompng tompng Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current colorization is:
Image

Array(1) Kernel::Array(1) are definitely a method call, but I think it might be better colored as constant.

In an explicit method call case Kernel.Array(), Kernel&.Array(), it may be better colored as method_name.
What do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

I've fixed it in 0b398c3.

shugo added 3 commits March 25, 2026 17:22
Colorize method name `foo` in the following code:

```
a.foo += 1
a.foo &&= 1
a.foo ||= 1
```
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates IRB’s syntax colorization to visually distinguish method calls by applying a dedicated style to method/message names during AST traversal.

Changes:

  • Add a new message_name token style and colorize method/message names for Prism call nodes in IRB::Color.colorize_code.
  • Add a colorize_call: keyword to enable/disable call highlighting (disabled for IRB::ColorPrinter output).
  • Update IRB colorization tests and adjust an integration-test regex to tolerate ANSI color sequences around binding.irb.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
lib/irb/color.rb Introduces message_name styling and Prism visitor logic to highlight method/message names; adds colorize_call: option.
lib/irb/color_printer.rb Disables call highlighting when pretty-printing fragments to avoid coloring method-like strings in printer output.
test/irb/test_color.rb Updates/expands expectations to reflect method-name highlighting behavior.
test/irb/helper.rb Updates breakpoint detection regex to match binding.irb even when colored.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

lib/irb/color.rb Outdated
Comment on lines +313 to +316
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(node.name)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colorize_call calls Regexp#match? with node.name. In Prism, CallNode#name is commonly a Symbol (e.g., :[]=, :+), and passing a Symbol to match? raises TypeError. Consider normalizing once (e.g., name_str = node.name.to_s and name_sym = node.name.is_a?(Symbol) ? node.name : node.name.to_sym) and using name_sym for OPERATORS.include? and name_str for the uppercase check.

Suggested change
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(node.name)
name = node.name
name_sym = name.is_a?(Symbol) ? name : name.to_sym
name_str = name.to_s
if node.call_operator_loc.nil? && OPERATORS.include?(name_sym)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(name_str)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides that, CallOperatorWriteNode seems not to have the name method. I'll fix it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in db9cd4c.

Regexp#match? accepts symbols, so there's no need to call to_s.

lib/irb/color.rb Outdated
Comment on lines +272 to +285
def visit_call_operator_write_node(node)
dispatch node.message_loc, :message_name if @colorize_call
super
end

def visit_call_and_write_node(node)
dispatch node.message_loc, :message_name if @colorize_call
super
end

def visit_call_or_write_node(node)
dispatch node.message_loc, :message_name if @colorize_call
super
end
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't use alias to keep super dispatching to the right visit_ method.
However, it seems that NestingParser uses alias, relying on the fact that the current implementation of all visit_* in Prism::Visitor is the same.

@st0012
Copy link
Copy Markdown
Member

st0012 commented Mar 26, 2026

I like having colors to distinguish method calls and local variables, but picking the right color is pretty tricky, especially for tokens as common as method calls.

Ideally, with this change we want to have:

  1. The same color between method name in definition (currently yellow) and method name in call (introduced as blue here)

  2. Different colors between method calls and common adjacent tokens. For example:

    a = 1
    Foo.bar(a)

    Foo, bar, and a should each have a distinctive color. Currently in the PR both Foo and bar are blue

  3. If we combine both 1 and 2, we should color method calls as yellow. Though it kinda hurts my eye with the light yellow my terminals default to:
    Screenshot 2026-03-26 at 12 36 54

    (If we can dim it a bit it would be perfect 😢)

So TL;DR - I think yellow would be a better color for both consistency and visual distinction.

@shugo
Copy link
Copy Markdown
Member Author

shugo commented Mar 26, 2026

@st0012 Thanks for your feed back.

Ideally, with this change we want to have:

  1. The same color between method name in definition (currently yellow) and method name in call (introduced as blue here)

The current color of method name in definition is blue, isn't it?

method_name: [BLUE, BOLD],

irb_screenshot

(On my terminal, WezTerm, characters with bold attribute are brighter than the ones without bold attr.)

I chose blue because it's used for method name in definition, but I have no strong opinion.

Different colors between method calls and common adjacent tokens. For example:

It might be good if method name name in definition were highlighted with a different color such as magenta.

@st0012
Copy link
Copy Markdown
Member

st0012 commented Mar 26, 2026

Oh wait I changed the color different times for testing and forgot to change back, sorry 🤦‍♂️

So now 1 is satisfied. I think it'd be great if we can make 2 happen too. WDYT changing constants to another color?

@shugo
Copy link
Copy Markdown
Member Author

shugo commented Mar 27, 2026

So now 1 is satisfied. I think it'd be great if we can make 2 happen too. WDYT changing constants to another color?

It sounds good to me.

I noticed that ENV is specifically highlighted in cyan.
How about magenta for other constants?

@st0012
Copy link
Copy Markdown
Member

st0012 commented Mar 27, 2026

  • Magenta:
Screenshot 2026-03-27 at 17 18 20
  • Yellow:
Screenshot 2026-03-27 at 17 18 44
  • Cyan:
Screenshot 2026-03-27 at 17 20 23

Not a fan of green + magenta when defining a class, but yellow is too bright and cyan is too close to blue. So yeah I'd go with magenta for now and we can later implement theme feature to support more color combinations.

@tompng Any preference?

@eregon
Copy link
Copy Markdown
Member

eregon commented Mar 27, 2026

IMO Cyan looks good, it's close to blue but easily differentiable still.

@tompng
Copy link
Copy Markdown
Member

tompng commented Mar 27, 2026

yellow is too bright

Agree.
I like magenta. I also like cyan. they both looks nice to me.
For ENV, I don't think ENV is a special constant compared to ARGF, ARGV, STDIN, STDOUT, STDERR.
Maybe we can make all constants the same color with bold and underline.

@eregon
Copy link
Copy Markdown
Member

eregon commented Mar 27, 2026

In the Cyan screenshot, nil is underlined, but nil isn't a constant it's a keyword so probably it shouldn't be underlined?

Magenta looks OK to me too, and might be better to differentiate keywords & constants.

For ENV, I don't think ENV is a special constant compared to ARGF, ARGV, STDIN, STDOUT, STDERR.
Maybe we can make all constants the same color with bold and underline.

👍

@st0012
Copy link
Copy Markdown
Member

st0012 commented Mar 27, 2026

I'm also fine with cyan if that's what people choose 👍

@st0012 st0012 added the enhancement New feature or request label Mar 27, 2026
@shugo
Copy link
Copy Markdown
Member Author

shugo commented Mar 27, 2026

Thanks for your comments.

Green + magenta looks fine on my terminal, but seem not look good on Stan's, so I'd like to choose cyan for method names and highlight ENV the same as other constants.

shugo added 4 commits March 28, 2026 10:49
It was not used to keep `super` dispatch to the right `visit_` method.
However, it seems that `NestingParser` uses `alias`, relying on the
fact that the current implementation of all `visit_*` in `Prism::Visitor`
is the same.
@shugo
Copy link
Copy Markdown
Member Author

shugo commented Mar 30, 2026

Green + magenta looks fine on my terminal, but seem not look good on Stan's, so I'd like to choose cyan for method names and highlight ENV the same as other constants.

I've addressed these issues and have supported alias in
https://github.com/ruby/irb/pull/1189/changes/db9cd4cab32198435d60d1911a960218b8bf0cac..2c7df4dd2488ff55e7fa74b2c85d8d374e291392

super(@colorize ? Color.colorize(str, [:GREEN]) : str, width)
else
super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width)
super(@colorize ? Color.colorize_code(str, ignore_error: true, colorize_call: false) : str, width)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not want to colorize call in this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above code was introduced to fix the following failure of the debug compatibility test:

https://github.com/ruby/irb/actions/runs/23522320220/job/68468010147

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I don't like that we update IRB lib code to workaround a test.

I'd prefer:

  1. Ship without this workaround
  2. Propose test changes to debug
  3. Let CI fail for a few days until 2 is merged

An alternative could be to disable this test until 2 is merged.

@tompng WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's correct to highlight member names as method names in the Struct inspect output.

Furthermore, the results might differ in environments where variables with the same names as the members are defined.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, the results might differ in environments where variables with the same names as the members are defined.

This might be an overthought, as no local variables are specified here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants