Sublime Forum

Best way to deal with highlighting multi-line function definitions

#21

Thanks I think I got it now. You’ve been really helpful as always.

0 Likes

#22

Hah! Thanks for the link

0 Likes

#23

Ah, thanks, that works! So it is not possible to add an underline using a colour scheme?

0 Likes

#24

By the way, do you know Sublime Linter? It will automatically mark errors and warnings, you’d only need to write a simple plug-in to have it get the errors and warnings from your compiler or other external linter. It also has pop-ups.

0 Likes

#25

That is correct; currently the only font effects that can be applied via the color scheme are bold and italic, although it has been mentioned that the next series of builds is going to include a glow effect as well.

0 Likes

#26

Getting back to this today and found another issue. Currently I need to keep track of the warnings by line number so I can reference them for on_hover events. This is fine of course unless the document changes lines and then things get out of sync.

Is there a way to assign arbitrary data to the regions so I could reference the region by some unique ID? If ST gave each region a unique ID that was returned from add_regions that would be good also but I don’t see that’s the case.

0 Likes

#27

Thanks for the tip. I never heard of it before but maybe I should go that route.

0 Likes

#28

Technically, I referred to it in the original thread that linked to this one as something that does the thing you’re trying to do here. :wink:

0 Likes

#29

egads! I just realized I started posted my replies to this thread instead of the topic I started! I don’t know how I didn’t notice. Sorry about that.

0 Likes

#30

did you see my latest question about line numbers? I posted it to this topic so I’m not sure you’ve even noticed. again sorry about that.

0 Likes

#31

I didn’t notice that myself until we’d gone back and forth a few times. The dangers of doing two things at once. :grin:

I did see that question, yes. You can’t add arbitrary data to the regions, but you can use their index in the region list as an index into another data structure though.

Note however that one they’re applied, regions will follow the area that they’re attached to. So if someone adds or removes lines after you apply the regions, they’ll maintain their relative position and keep tracking the same location.

0 Likes

#32

Sublime Linter re-applies the linting every time you edit the code (well, as soon as you stop typing, with a small delay, to make sure it won’t trigger several times a second while you’re typing). So that solves any line-number issues. They have really thought of everything!

0 Likes

#33

So I can get the region list at the given point from on_hover? Right now I was just getting the line number and using that. I’ll need to investigate some more.

0 Likes

#34

Looking forward to this glow effect! I wonder why they won’t allow various underline styles in colour schemes. Perhaps they think underlines might interfere with other aspects to the editor, like adding regions the way we’re doing here.

0 Likes

#35

I’m still not getting how this works. I can get the index of the warning in the region list but I don’t know how to find the region again in on_hover. If the region object could be used a hash key then perhaps this would work but I don’t know if that’s possible even.

0 Likes

#36

This might make it a bit clearer:

import sublime
import sublime_plugin


class ApplyMarksCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        # Create a list that contains the text from each selection.
        text = [self.view.substr(r) for r in self.view.sel()]

        # Set the list into a view setting to save it for later.
        self.view.settings().set("_sel_text", text)

        # Add regions now to underline the text that was selected.
        self.view.add_regions("selected", self.view.sel(), "region.redish",
                               flags=sublime.DRAW_NO_FILL|
                                     sublime.DRAW_NO_OUTLINE|
                                     sublime.DRAW_SOLID_UNDERLINE)


class HoverListener(sublime_plugin.EventListener):
    def on_hover(self, view, point, hover_zone):
        # Only trigger while hovering on text
        if hover_zone != sublime.HOVER_TEXT:
            return

        # Get the text we saved in the settings; if there isn't any, then
        # do nothing and leave.
        text = view.settings().get("_sel_text")
        if text is None:
            return;

        # Get the list of regions and iterate over it looking to see if the
        # cursor is hovering on one
        marks = view.get_regions("selected")
        for idx, region in enumerate(marks):
            # If this region contains the point of the hover, then display a
            # message
            if region.contains(point):
                view.show_popup("Hovering on '%s'" % text[idx],
                                sublime.HIDE_ON_MOUSE_MOVE_AWAY,
                                point, 512, 256)

In this example, the apply_marks command adds regions based on the selections (so make sure to select some text before you run it :wink:) and for expediency it saves them into a setting in the view. That could just as easily be any sort of data structure but the important part is:

  • The list contains whatever it is you want to access in the hover (here it’s just text)
  • The list is ordered from the top down because that’s how the regions are going to appear later

In the hover listener, we leave immediately if the hover event isn’t hovering over text, and we also do nothing if we don’t have any information associated with the view. Here that’s detected by looking at a setting, but you could also look that up based on the name of the file or such.

If there is a list of items, then we ask the view for a list of our regions and check to see which one of them contains the point that the user is hovering the mouse over. Assuming you’re regions don’t overlap, that will only be one of the regions (if any at all).

Once we have the index of the region, we can look in our data structure to see what’s at that location and use it to generate the popup. In this case it just tells you what text was there when the region was created.

sublime.Region() is not hashable at the moment, but since it’s just a pair of numbers you can use a tuple in it’s place:

>>> r = sublime.Region(12, 14)
>>> x = {}
>>> x[(r.a, r.b)] = "Hello"
>>> x
{(12, 14): 'Hello'}
0 Likes

#37

I see the missing parts now (get_regions and region.contains namely). I’ll try this tomorrow when I find some time.

0 Likes

#38

Think I got this working now! Just curious, why do you use self.view.settings() instead of putting a global var at the top level namespace (that’s what I was doing)? They both seem to work but I don’t know how safe the global is against getting trashed out of memory at some point.

0 Likes

#39

Indeed any global variables will be clobbered when you quit Sublime or if your plugin reloads (say if you’re actively developing it). It was expedient to save the data there for that reason and also just as a demonstration that it’s a thing that you can do; settings are persisted in the session information so you can use that to carry state between restarts and reloads if you want to.

Note however that by default regions that you add via add_regions() are not persisted in the session, so in this example although the data is saved across restarts the regions aren’t. You can add sublime.PERSISTENT to the flags that you pass to add_regions() to tell it to persist them into the session information.

For the case of something like what you’re doing here it may not make sense to persist either one, since in between restarts any number of changes might have been made to the file, which would invalidate the information you’re saving anyway.

The exec command keeps an instance variable in its own command class that stores the information, and it has an argument you can invoke it with that just updates the error displays based on that information without doing anything else, so that updates can be triggered without saving the data globally.

0 Likes

#40

I’ve implemented this but I don’t think region.contains is returning the correct region. I understand I copied just fragments of the code but maybe that’s good enough to make sense of it. You can see how add_warning is adding the regions in order but when I enumerate over them in on_hover the indexes are all wrong.

Did I add the regions incorrectly? Not sure why this is happening.

==========

added region #0 at AppDelegate.pas:149
added region #1 at AppDelegate.pas:150
added region #2 at AppDelegate.pas:29
added region #3 at AppDelegate.pas:30

region #3 at AppDelegate.pas:150
region #2 at AppDelegate.pas:149

def on_hover(self, view, point, hover_zone):
  file_name = os.path.basename(view.file_name())
  marks = view.get_regions("warning")
  for idx, region in enumerate(marks):
    if region.contains(point):
      message = get_warning(file_name, idx)
      print("region #"+str(idx)+" at "+file_name+":"+str(view.rowcol(point)[0] + 1))
      if message != None:
        self.show_warning_popup(view, message, point)


def add_warning(view, file_name, line_num, message):
  line_pos = view.text_point(int(line_num) - 1, 0)
  line_region = view.line(line_pos)
  regions = view.get_regions("warning");
  key = str(len(regions));
  view.add_regions("warning", regions + [line_region], "comment.warning", icon="dot", flags=sublime.DRAW_STIPPLED_UNDERLINE | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE)
  if not (file_name in build_messages):
    build_messages[file_name] = dict()
  print("added region #"+key+" at "+file_name+":"+str(line_num))
  build_messages[file_name][key] = message
0 Likes