BracketHighlighter icon indicating copy to clipboard operation
BracketHighlighter copied to clipboard

Ruby endless method syntax

Open samrjenkins opened this issue 3 years ago β€’ 3 comments

Description

New Ruby endless method syntax breaks the Bracket Highlighter:

def method_name() = method_definition

It would appear that the Bracket Highlighter is looking for an end keyword to pair with the def. However, the new endless method syntax does not require this end.

Screenshot 2022-01-30 at 14 22 28

Support Info

  • ST ver.: 4126
  • Platform: osx
  • Arch: x64
  • Plugin ver.: 2.29.2
  • Install via PC: True
  • mdpopups ver.: 4.2.1
  • backrefs ver.: 5.0
  • markdown ver.: 3.2.2
  • pygments ver.: 2.1a0
  • jinja2 ver.: 2.10.1

samrjenkins avatar Jan 30 '22 14:01 samrjenkins

Ugh. I hate this syntax already 😒. I'm not really a ruby user, so I'm obviously not familiar with current changes in ruby land πŸ™‚.

Please leave some appropriate references that explain nuances of new syntax. Ideally, a ruby user will submit a pull request, but if not, assuming I have appropriate references, and have the time, I might be able to provide a solution.

facelessuser avatar Jan 30 '22 14:01 facelessuser

This may be extremely difficult to pull off with a simple regex. It may be that we need to analyze the function definition line of code for each def case now as we would have to find the end of the function parameters to then check for an equal sign after them (as equal signs can occur within the function parameters. Depending on how well the language scopes things, this could be easy-ish, or very difficult.

facelessuser avatar Jan 30 '22 15:01 facelessuser

It's been over 2 months and no additional, requested references have been posted. I'm not really interested in doing a bunch of research on a language I do not use.

Sadly there is also no syntax scoping to differentiate endless methods from methods with ends. The only way to be sure is to parse the code for context.

I think this is still possible, but we'll have to look to capture the function name and then all parameters. It is likely we can capture the full extent of meta.function.parameters and then check for an = afterward. This is of course potentially naive as I don't use Ruby and I'm uncertain of additional "gotcha" cases, but when I get some time, I guess I'll at least try to handle this case.

facelessuser avatar Apr 16 '22 15:04 facelessuser

Hey @facelessuser! I just found this issue and tried to come up with a solution for it. I edited rubykeywords.rb and managed to fix those errors on def keywords, but I couldn't figure out how this extension matches end keywords.

For example, assuming this file:

module Foo
  class Error < StandardError; end

  module_function

  def setup(&) = instance.set_setup_block(&)

  def build(&) = instance.set_run_block(&)

  def run! = instance.run!

  def instance = @instance ||= Runner.new
end

It is rendered normally, without errors:

image

Placing the cursor on an "endless" def also works:

image

But it gets lost on that last end:

image

And fails to match it with the initial module:

image

Would you be so kind as to provide where I could look for the logic for fixing that last case? Any help would be super appreciated! <3

In case it is of any help, this is the final rubykeywords.py: I just tried to return none, but again, I don't know much about this codebase, was basically a shot in the dark. πŸ™ˆ

"""
BracketHighlighter.

Copyright (c) 2013 - 2016 Isaac Muse <[email protected]>
License: MIT
"""
import re

RE_DEF = re.compile(r"\s*(?:(?:private|public|protected)\s+)?(def).*?")
RE_DEF_ENDLESS = re.compile(r"\s*((?:private|public|protected)\s+)?def\s+(?:[\w\.=]+)[?!]?\s*(\([^)]+\)\s*|\s+)=")
RE_KEYWORD = re.compile(r"(\s*\b)[\w\W]*")
SPECIAL_KEYWORDS = ('do',)
NORMAL_KEYWORDS = ('for', 'until', 'unless', 'while', 'class', 'module', 'if', 'begin', 'case')


def post_match(view, name, style, first, second, center, bfr, threshold):
    """Strip whitespace from being targeted with highlight."""

    if first is not None:
        # Strip whitespace from the beginning of first bracket
        open_bracket = bfr[first.begin:first.end]
        if open_bracket not in SPECIAL_KEYWORDS:
            open_bracket_stripped = open_bracket.strip()
            if open_bracket_stripped not in NORMAL_KEYWORDS:
                
                # Try to detect endless `def`s
                after_bracket = bfr[first.begin + 1:]
                bracket_line = after_bracket[:after_bracket.index("\n")]
                m = RE_DEF_ENDLESS.match(bracket_line)
                if m:
                    return None, None, None
                # -----------------------------------
                m = RE_DEF.match(open_bracket)
                if m:
                    first = first.move(first.begin + m.start(1), first.begin + m.end(1))
            else:
                m = RE_KEYWORD.match(open_bracket)
                if m:
                    first = first.move(first.begin + m.end(1), first.end)
    return first, second, style

heyvito avatar Feb 08 '23 20:02 heyvito

@heyvito Thanks for the examples! I'll try to take a closer look this week and see if I can offer some suggestions.

facelessuser avatar Feb 08 '23 20:02 facelessuser

@facelessuser Awesome! Please let me know in case I can assist with anything! <3

heyvito avatar Feb 08 '23 23:02 heyvito

Thanks guys!

samrjenkins avatar Feb 09 '23 09:02 samrjenkins

FYI, I haven't forgotten about this, I've just been very busy 😞. I think I've cleared my plate and can probably take a closer look at this now.

facelessuser avatar Feb 27 '23 14:02 facelessuser

I imagine we are going to have to anchor def to =. We may need to have a def that matches with a forward lookahead that ensures def is followed by an endless signature, anchored by the end of =, and then adjust the highlight in post-match. Non-endless def will match as well, but will instead be anchored to end.

This is just my initial thought. I imagine someone could construct an endless that doesn't match as you can have default methods in a function with strings that can contain anything, but again, maybe this isn't common with endless? 🀷🏻 I don't use Ruby, so I don't actually know. Maybe something that works most of the time is better than something that doesn't work at all. We'll see what I can cook up.

facelessuser avatar Feb 27 '23 18:02 facelessuser

We might be able to do some scope checking to simplify things...I'm optimistic.

facelessuser avatar Feb 27 '23 18:02 facelessuser

I'm not sure how well this works in all cases, but I have something working. It has to use the def and the = in an endless def. The current plugin API was not sufficient to capture what we needed to, so I had to extend validate to take a view.

Once we had that, we could validate = by walking back a little and seeing if we found a ruby function or ruby function parameters. If we did, we knew we could count that = as valid.

Ruby keeps getting more and more complicated to highlight:

ruby-endless

I need time to consider how the final solution will work. I have no idea if there are reasonable cases where this would break either.

facelessuser avatar Mar 05 '23 20:03 facelessuser

@facelessuser you are just awesome. There will be more and more people migrating to ruby >=3.0 and facing the issue like I just did. Thank you for your effort!

pyromaniac avatar Mar 26 '23 07:03 pyromaniac

I still think the Ruby rule is currently kind of a mess, but this definitely allows for endless methods. Since we have to capture context before and after start/ends in Ruby, we have cases where you can have your cursor before a start and still get a highlight. It'd be nice if we had some sort of event to test if the cursor is within a sub-section of the match, but that's probably a task for another day.

I have no idea if there are corner cases this fails at, but I probably need to get it out the door to let people play with it.

facelessuser avatar Mar 26 '23 14:03 facelessuser

I'd really like to restructure some things in the bracket plugin API to clean up how Ruby stuff works, but this shouldn't hold us up from getting endless support out. The next release will implement the endless support that I currently have. PR is up.

facelessuser avatar Aug 05 '23 15:08 facelessuser

Thank you so much for sorting this out, @facelessuser! You rock! ❀️

heyvito avatar Aug 05 '23 16:08 heyvito

@facelessuser you’re a hero! Many thanks for your work on this 😎

samrjenkins avatar Aug 06 '23 14:08 samrjenkins