Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions plugins/lib/view_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sublime import Region
from sublime import RegionFlags

__all__ = ['has_file_ext', 'base_scope',
'coorded_substr', 'get_text',
Expand Down Expand Up @@ -165,3 +166,14 @@ def extract_selector(view, selector, point):
if reg.contains(point):
return reg
return None


def region_flags_from_strings(flag_names):
"""Convert a list of strings into ``sublime.RegionFlags`` object.

Note: Invalid flag names are silently ignored.

Example:
flags: sublime.RegionFlags = region_flags_from_strings(["DRAW_EMPTY", "DRAW_NO_FILL"])
"""
return RegionFlags(sum(getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names))
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return RegionFlags(sum(getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names))
return sum(getattr(RegionFlags, n, RegionFlags.NONE) for n in set(flag_names))

Isn't the outer RegionFlags call redundant since we're already getting and summing RegionFlags "instances"? Also wrapping in a set to deduplicate the strings and prevent unintended side effects.

Copy link
Member Author

Choose a reason for hiding this comment

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

Runtime yes, typechecker/linter no. Without that pyright/basedpyright complains about any assignments.

Copy link
Member

@FichteFoll FichteFoll Jan 22, 2026

Choose a reason for hiding this comment

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

I wonder if they can they handle functools.reduce(operator.or_, (getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names), RegionFlags.NONE).

Copy link
Member Author

@deathaxe deathaxe Jan 22, 2026

Choose a reason for hiding this comment

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

It technically works, but looks more noisy with more dependencies involved and is 2.5x slower.

It's however an ideal example of how "modern" python bloats and overcomplicates simple tasks undermining any attempts by core team to improve performance.

Copy link
Member

@FichteFoll FichteFoll Jan 22, 2026

Choose a reason for hiding this comment

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

Yes, this was not meant as a change request. I was just thinking out loud.

However, the set issue is still open because currently trying to resolve region_flags_from_strings(["DRAW_EMPTY", "DRAW_EMPTY"]) will have very undesired effects. Using reduce(operator.or_, …) would address this, but so would wrapping the iterable in a set(…).

Copy link
Member Author

Choose a reason for hiding this comment

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

What's the set issue?

The verbose way to implement the function using official RegionFlags semantics would be ...

def region_flags_from_strings(flag_names):
    result = RegionFlags.NONE
    for name in flag_names:
        try:
            result |= RegionFlags[name]
        except KeyError:
            pass

    return result

But that's still 2x slower.


import functools
import operator
from sublime import RegionFlags
import timeit

def merge_sum(flag_names: list[str]) -> RegionFlags:
    return sum(getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names)

def merge_sum_cast(flag_names: list[str]) -> RegionFlags:
    return RegionFlags(sum(getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names))

def merge_reduce(flag_names: list[str]) -> RegionFlags:
    return functools.reduce(operator.or_, (getattr(RegionFlags, n, RegionFlags.NONE) for n in flag_names), RegionFlags.NONE)

def region_flags_from_strings(flag_names: list[str]) -> RegionFlags:
    result = RegionFlags.NONE
    for name in flag_names:
        try:
            result |= RegionFlags[name]
        except KeyError:
            pass
    return result

print(timeit.timeit(lambda: merge_sum(["DRAW_EMPTY", "HIDE_ON_MINIMAP"])))
# > 0.34730920000583865
print(timeit.timeit(lambda: merge_sum_cast(["DRAW_EMPTY", "HIDE_ON_MINIMAP"])))
# > 0.601449900001171
print(timeit.timeit(lambda: merge_reduce(["DRAW_EMPTY", "HIDE_ON_MINIMAP"])))
# > 1.6256988999957684
print(timeit.timeit(lambda: region_flags_from_strings(["DRAW_EMPTY", "HIDE_ON_MINIMAP"])))
# > 1.3742471000005025

5 changes: 2 additions & 3 deletions plugins/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
import sublime
import sublime_plugin

from sublime_lib.flags import RegionOption

from ..lib import get_setting, inhibit_word_completions
from ..lib import syntax_paths
from ..lib.view_utils import region_flags_from_strings
from ..lib.weakmethod import WeakMethodProxy

from .region_math import (VALUE_SCOPE, KEY_SCOPE, KEY_COMPLETIONS_SCOPE,
Expand Down Expand Up @@ -265,7 +264,7 @@ def do_linting(self):
unknown_regions,
scope=get_setting('settings.highlight_scope', "text"),
icon='dot',
flags=RegionOption(*styles)
flags=region_flags_from_strings(styles)
)
else:
self.view.erase_regions('unknown_settings_keys')
Expand Down
15 changes: 5 additions & 10 deletions plugins/syntax_dev/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import sublime
import sublime_plugin

from sublime_lib.flags import RegionOption

from ..lib import package_settings
from ..lib import syntax_paths
from ..lib.view_utils import region_flags_from_strings

__all__ = (
'SyntaxDefRegexCaptureGroupHighlighter',
Expand All @@ -23,17 +23,12 @@ def is_applicable(cls, settings):
return settings.get('syntax') == syntax_paths.SYNTAX_DEF

def on_selection_modified(self):
prefs = sublime.load_settings('PackageDev.sublime-settings')
scope = prefs.get('syntax.captures_highlight_scope', "text")
styles = prefs.get('syntax.captures_highlight_styles', ['DRAW_NO_FILL'])

style_flags = RegionOption(*styles)

prefs = package_settings()
self.view.add_regions(
key='captures',
regions=list(self.get_regex_regions()),
scope=scope,
flags=style_flags,
scope=prefs['syntax.captures_highlight_scope'],
flags=region_flags_from_strings(prefs['syntax.captures_highlight_styles']),
)

def get_regex_regions(self):
Expand Down
5 changes: 2 additions & 3 deletions plugins/syntaxtest_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import sublime
import sublime_plugin

from sublime_lib.flags import RegionOption

from .lib import get_setting, path_is_relative_to
from .lib.view_utils import region_flags_from_strings

__all__ = (
'SyntaxTestHighlighterListener',
Expand Down Expand Up @@ -200,7 +199,7 @@ def on_selection_modified_async(self):

scope = get_setting('syntax_test.highlight_scope', 'text')
styles = get_setting('syntax_test.highlight_styles', ['DRAW_NO_FILL'])
style_flags = RegionOption(*styles)
style_flags = region_flags_from_strings(styles)

self.view.add_regions('current_syntax_test', [region], scope, '', style_flags)

Expand Down