-
Notifications
You must be signed in to change notification settings - Fork 597
[CSS] upgrade to py3.13 #4368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jrappen
wants to merge
5
commits into
sublimehq:master
Choose a base branch
from
jrappen:fix-css
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
[CSS] upgrade to py3.13 #4368
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ee4b225
[CSS] upgrade to py3.13
jrappen afec21b
[CSS] address pull request comments
jrappen a542c5f
[CSS] address pull request comments
jrappen 971997a
[CSS] address pull request comments
jrappen 079a6d7
[CSS] address pull request comments
jrappen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 3.8 | ||
| 3.13 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import re | ||
| import sublime | ||
| import sublime_plugin | ||
|
|
||
| import re | ||
| import timeit | ||
|
|
||
| from functools import cached_property, wraps | ||
|
|
@@ -17,63 +18,63 @@ | |
|
|
||
|
|
||
| def timing(func): | ||
| @wraps(func) | ||
| @wraps(wrapped=func) | ||
| def wrap(*args, **kw): | ||
| if ENABLE_TIMING: | ||
| ts = timeit.default_timer() | ||
| ts: float = timeit.default_timer() | ||
| result = func(*args, **kw) | ||
| if ENABLE_TIMING: | ||
| te = timeit.default_timer() | ||
| te: float = timeit.default_timer() | ||
| print(f"{func.__name__}({args}, {kw}) took: {1000.0 * (te - ts):2.3f} ms") | ||
| return result | ||
| return wrap | ||
|
|
||
|
|
||
| def match_selector(view, pt, scope): | ||
| def match_selector(view: sublime.View, pt: int, scope: str) -> bool: | ||
| # This will catch scenarios like: | ||
| # - .foo {font-style: |} | ||
| # - <style type="text/css">.foo { font-weight: b|</style> | ||
| return any(view.match_selector(p, scope) for p in (pt, pt - 1)) | ||
| return any(view.match_selector(pt=p, selector=scope) for p in (pt, pt - 1)) | ||
|
|
||
|
|
||
| def next_none_whitespace(view, pt): | ||
| def next_none_whitespace(view: sublime.View, pt: int) -> str | None: | ||
| for pt in range(pt, view.size()): | ||
| ch = view.substr(pt) | ||
| if ch not in ' \t': | ||
| return ch | ||
| return None | ||
|
|
||
|
|
||
| class CSSCompletions(sublime_plugin.EventListener): | ||
|
|
||
| @cached_property | ||
| def func_args(self): | ||
| def func_args(self) -> dict: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice to also have the dict generics here (and below). |
||
| return completions.get_func_args() | ||
|
|
||
| @cached_property | ||
| def props(self): | ||
| def props(self) -> dict: | ||
| return completions.get_properties() | ||
|
|
||
| @cached_property | ||
| def re_name(self): | ||
| def re_name(self) -> re.Pattern[str]: | ||
| return re.compile(r"([a-zA-Z-]+)\s*:[^:;{}]*$") | ||
|
|
||
| @cached_property | ||
| def re_value(self): | ||
| def re_value(self) -> re.Pattern[str]: | ||
| return re.compile(r"^(?:\s*(:)|([ \t]*))([^:]*)([;}])") | ||
|
|
||
| @timing | ||
| def on_query_completions(self, view, prefix, locations): | ||
|
|
||
| settings = sublime.load_settings('CSS.sublime-settings') | ||
| if settings.get('disable_default_completions'): | ||
| def on_query_completions(self, view: sublime.View, prefix: str, locations: list[int]) -> sublime.CompletionList | None: | ||
| settings: sublime.Settings = sublime.load_settings(base_name='CSS.sublime-settings') | ||
| if settings.get(key='disable_default_completions'): | ||
| return None | ||
|
|
||
| selector = settings.get('default_completions_selector', '') | ||
| selector = settings.get(key='default_completions_selector', default='') | ||
| if isinstance(selector, list): | ||
| selector = ''.join(selector) | ||
|
|
||
| pt = locations[0] | ||
| if not match_selector(view, pt, selector): | ||
| pt: int = locations[0] | ||
| if not match_selector(view, pt, scope=selector): | ||
| return None | ||
|
|
||
| if match_selector(view, pt, "meta.property-value.css meta.function-call.arguments"): | ||
|
|
@@ -84,11 +85,11 @@ def on_query_completions(self, view, prefix, locations): | |
| items = self.complete_property_name(view, prefix, pt) | ||
|
|
||
| if items: | ||
| return sublime.CompletionList(items) | ||
| return sublime.CompletionList(completions=items) | ||
| return None | ||
|
|
||
| def complete_property_name(self, view, prefix, pt): | ||
| text = view.substr(sublime.Region(pt, view.line(pt).end())) | ||
| def complete_property_name(self, view: sublime.View, prefix: str, pt: int) -> list[sublime.CompletionItem]: | ||
| text = view.substr(x=sublime.Region(a=pt, b=view.line(x=pt).end())) | ||
| matches = self.re_value.search(text) | ||
| if matches: | ||
| colon, space, value, term = matches.groups() | ||
|
|
@@ -99,28 +100,28 @@ def complete_property_name(self, view, prefix, pt): | |
| term = "" | ||
|
|
||
| # don't append anything if next character is a colon | ||
| suffix = "" | ||
| suffix: str = "" | ||
| if not colon: | ||
| # add space after colon if smart typing is enabled | ||
| if not space and view.settings().get("auto_complete_trailing_spaces"): | ||
| if not space and view.settings().get(key="auto_complete_trailing_spaces"): | ||
| suffix = ": $0" | ||
| else: | ||
| suffix = ":$0" | ||
|
|
||
| # terminate empty value if not within parentheses | ||
| if not value and not term and not match_selector(view, pt, "meta.group"): | ||
| if not value and not term and not match_selector(view, pt, scope="meta.group"): | ||
| suffix += ";" | ||
|
|
||
| return ( | ||
| return [ | ||
| sublime.CompletionItem( | ||
| trigger=prop, | ||
| completion=prop + suffix, | ||
| completion_format=sublime.COMPLETION_FORMAT_SNIPPET, | ||
| kind=KIND_CSS_PROPERTY | ||
| ) for prop in self.props | ||
| ) | ||
| ] | ||
|
|
||
| def complete_property_value(self, view, prefix, pt): | ||
| def complete_property_value(self, view: sublime.View, prefix: str, pt: int) -> list[sublime.CompletionItem]: | ||
| completions = [ | ||
| sublime.CompletionItem( | ||
| trigger="!important", | ||
|
|
@@ -129,15 +130,15 @@ def complete_property_value(self, view, prefix, pt): | |
| details="override any other declaration" | ||
| ) | ||
| ] | ||
| text = view.substr(sublime.Region(view.line(pt).begin(), pt - len(prefix))) | ||
| text: str = view.substr(x=sublime.Region(a=view.line(x=pt).begin(), b=pt - len(prefix))) | ||
| matches = self.re_name.search(text) | ||
| if matches: | ||
| prop = matches.group(1) | ||
| values = self.props.get(prop) | ||
| if values: | ||
| details = f"<code>{prop}</code> property-value" | ||
|
|
||
| if match_selector(view, pt, "meta.group") or next_none_whitespace(view, pt) == ";": | ||
| if match_selector(view, pt, scope="meta.group") or next_none_whitespace(view, pt) == ";": | ||
| suffix = "" | ||
| else: | ||
| suffix = "$0;" | ||
|
|
@@ -161,25 +162,25 @@ def complete_property_value(self, view, prefix, pt): | |
|
|
||
| return completions | ||
|
|
||
| def complete_function_argument(self, view: sublime.View, prefix, pt): | ||
| def complete_function_argument(self, view: sublime.View, prefix: str, pt: int) -> list[sublime.CompletionItem]: | ||
| func_name = "" | ||
| nest_level = 1 | ||
| # Look for the beginning of the current function call's arguments list, | ||
| # while ignoring any nested function call or group. | ||
| for i in range(pt - 1, pt - 32 * 1024, -1): | ||
| ch = view.substr(i) | ||
| ch: str = view.substr(x=i) | ||
| # end of nested arguments list or group before caret | ||
| if ch == ")" and not view.match_selector(i, "string, comment"): | ||
| if ch == ")" and not view.match_selector(pt=i, selector="string, comment"): | ||
| nest_level += 1 | ||
| continue | ||
| # begin of maybe nested arguments list or group before caret | ||
| if ch == "(" and not view.match_selector(i, "string, comment"): | ||
| if ch == "(" and not view.match_selector(pt=i, selector="string, comment"): | ||
| nest_level -= 1 | ||
| # Stop, if nesting level drops below start value as this indicates the | ||
| # beginning of the arguments list the function name is of interest for. | ||
| if nest_level <= 0: | ||
| func_name = view.substr(view.expand_by_class( | ||
| i - 1, sublime.CLASS_WORD_START | sublime.CLASS_WORD_END)) | ||
| func_name: str = view.substr(x=view.expand_by_class( | ||
| x=i - 1, classes=sublime.CLASS_WORD_START | sublime.CLASS_WORD_END)) | ||
| break | ||
|
|
||
| if func_name == "var": | ||
|
|
@@ -191,18 +192,18 @@ def complete_function_argument(self, view: sublime.View, prefix, pt): | |
| details="var() argument" | ||
| ) | ||
| for symbol in set( | ||
| view.substr(symbol_region) | ||
| for symbol_region in view.find_by_selector("entity.other.custom-property") | ||
| view.substr(x=symbol_region) | ||
| for symbol_region in view.find_by_selector(selector="entity.other.custom-property") | ||
| ) | ||
| if not prefix or symbol.startswith(prefix) | ||
| ] | ||
|
|
||
| args = self.func_args.get(func_name) | ||
| if not args: | ||
| return None | ||
| return [] | ||
|
|
||
| completions = [] | ||
| details = f"{func_name}() argument" | ||
| details: str = f"{func_name}() argument" | ||
| for arg in args: | ||
| if isinstance(arg, list): | ||
| completions.append(sublime.CompletionItem( | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ST 4201+ reads 3.8 as 3.13. Why not just leaving those files alone until py33 is removed?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having them say 3.8 when the files actually require 3.13 is just wrong, though, even when ST 4201+ can handle it. This should also prevent issues when trying to run these Python files in an older ST build that does not have the 3.13 runtime.
I would be in favor of just removing them, if they are not required, but the 3.3 host is not dead yet and still the default, afaik.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need typing in such simple plugins and beyond that the modules and this set of PRs doesn't add anything which judges breaking things for users which probably use cloned repos - at least not until public betas are available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing annotations are basically free, assuming you stick to the various Python version requirements for various features. I would certainly not be against adding them if they don't break anything, e.g. if they were compatible with 3.8. If we're doing strict upgrades to 3.13 syntax (and without
from __future__ import annotations), then I agree with you that breaking compatibility just for the heck of it isn't really worthwhile.Either way, the
.python-versionshould reflect the actually required Python version or be removed, imo.