Skip to content
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

Highlight keyword arguments in python call #10

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ hi semshiGlobal ctermfg=214 guifg=#ffaf00
hi semshiImported ctermfg=214 guifg=#ffaf00 cterm=bold gui=bold
hi semshiParameter ctermfg=75 guifg=#5fafff
hi semshiParameterUnused ctermfg=117 guifg=#87d7ff cterm=underline gui=underline
hi def link semshiKeywordArgument @parameter.python
hi semshiFree ctermfg=218 guifg=#ffafd7
hi semshiBuiltin ctermfg=207 guifg=#ff5fff
hi semshiAttribute ctermfg=49 guifg=#00ffaf
Expand Down
2 changes: 2 additions & 0 deletions plugin/semshi.vim
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ function! semshi#buffer_wipeout()
endfunction

function! semshi#init()
" Also update README.md
hi def semshiLocal ctermfg=209 guifg=#ff875f
hi def semshiGlobal ctermfg=214 guifg=#ffaf00
hi def semshiImported ctermfg=214 guifg=#ffaf00 cterm=bold gui=bold
hi def semshiParameter ctermfg=75 guifg=#5fafff
hi def semshiParameterUnused ctermfg=117 guifg=#87d7ff cterm=underline gui=underline
hi def link semshiKeywordArgument @parameter.python
hi def semshiFree ctermfg=218 guifg=#ffafd7
hi def semshiBuiltin ctermfg=207 guifg=#ff5fff
hi def semshiAttribute ctermfg=49 guifg=#00ffaf
Expand Down
1 change: 1 addition & 0 deletions rplugin/python3/semshi/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def group(s):
GLOBAL = group('global')
PARAMETER = group('parameter')
PARAMETER_UNUSED = group('parameterUnused')
KEYWORD = group('keywordArgument')
SELF = group('self')
IMPORTED = group('imported')
LOCAL = group('local')
Expand Down
25 changes: 23 additions & 2 deletions rplugin/python3/semshi/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from token import NAME, OP
from tokenize import tokenize

from .node import ATTRIBUTE, IMPORTED, PARAMETER_UNUSED, SELF, Node
from .node import ATTRIBUTE, IMPORTED, PARAMETER_UNUSED, SELF, KEYWORD, Node
from .util import debug_time

# Node types which introduce a new scope
Expand Down Expand Up @@ -89,6 +89,8 @@ def visit(self, node):
self._visit_except(node)
elif type_ in (ast.Import, ast.ImportFrom):
self._visit_import(node)
elif type_ is ast.keyword:
self._visit_keyword(node)
elif type_ is ast.arg:
self._visit_arg(node)
elif type_ in FUNCTION_BLOCKS:
Expand All @@ -99,7 +101,7 @@ def visit(self, node):
elif type_ in (ast.Global, ast.Nonlocal):
keyword = 'global' if type_ is ast.Global else 'nonlocal'
self._visit_global_nonlocal(node, keyword)
elif type_ is ast.keyword:
else:
pass

if type_ in (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef):
Expand Down Expand Up @@ -203,6 +205,25 @@ def _visit_class_meta(self, node):
self.visit(keyword)
del node.keywords

def _visit_keyword(self, node: ast.keyword):
"""Visit keyword argument in a function call."""
if node.arg:
# keyword=argument: highlight keyword
try:
lineno = node.lineno
col_offset = node.col_offset
except AttributeError:
# Python < 3.9 does not have node.lineno, etc.
# No highlight can be provided for keyworda rguments.
return
self.nodes.append(Node(
node.arg, lineno, col_offset, self._cur_env,
hl_group=KEYWORD
))
else:
# This is probably (**kwargs), do nothing
pass

def _visit_args(self, node):
"""Visit function arguments."""
# We'd want to visit args.posonlyargs, but it appears an internal bug
Expand Down
31 changes: 23 additions & 8 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest

from semshi import parser
from semshi.node import (ATTRIBUTE, BUILTIN, FREE, GLOBAL, IMPORTED, LOCAL,
from semshi.node import (ATTRIBUTE, BUILTIN, FREE, GLOBAL, IMPORTED, KEYWORD, LOCAL,
PARAMETER, PARAMETER_UNUSED, SELF, UNRESOLVED, Node,
group)
from semshi.parser import Parser, UnparsableError
Expand All @@ -18,6 +18,9 @@
# but as GLOBAL in Python 3.8.
MODULE_FUNC = GLOBAL if sys.version_info >= (3, 8) else LOCAL

filternone = lambda lst: [x for x in lst if x is not None]
py39 = lambda x: (x if sys.version_info >= (3, 9) else None)


def test_group():
assert group('foo') == 'semshiFoo'
Expand Down Expand Up @@ -172,9 +175,9 @@ def func2(j=k):
func(x, y=p, **z)
''')
root = make_tree(names)
assert root['names'] == [
'e', 'h', 'func', 'k', 'func2', 'func', 'x', 'p', 'z'
]
assert root['names'] == filternone([
'e', 'h', 'func', 'k', 'func2', 'func', 'x', py39('y'), 'p', 'z'
])
assert root['listcomp']['names'] == ['g', 'g']
assert root['func']['names'] == ['a', 'b', 'c', 'd', 'f', 'i']
assert root['func2']['names'] == ['j']
Expand All @@ -189,7 +192,7 @@ def f():
a
''')
root = make_tree(names)
assert root['names'] == ['a', 'A', 'x', 'z']
assert root['names'] == filternone(['a', 'A', 'x', py39('y'), 'z'])


def test_import_scopes_and_positions():
Expand Down Expand Up @@ -482,9 +485,10 @@ async def C():
pass
''')
root = make_tree(names)
assert root['names'] == [
'd1', 'a', 'c', 'A', 'd2', 'x', 'z', 'B', 'd3', 'C'
]
assert root['names'] == filternone([
'd1', 'a', py39('b'), 'c', 'A',
'd2', 'x', py39('y'), 'z', 'B', 'd3', 'C'
])

def test_global_builtin():
"""A builtin name assigned globally should be highlighted as a global, not
Expand Down Expand Up @@ -827,6 +831,17 @@ def test_posonlyargs_with_annotation():
assert [n.hl_group for n in names] == [MODULE_FUNC, UNRESOLVED, PARAMETER_UNUSED]


def test_keyword_arguments():
names = parse('foo(x, y, kwarg1=z, bar=dict(value=1))')
assert [n.name for n in names] == filternone([
'foo', 'x', 'y', py39('kwarg1'), 'z',
py39('bar'), 'dict', py39('value'),
])
for n in names:
if n.name == 'kwarg1' or n.name == 'value':
assert n.hl_group == KEYWORD


@pytest.mark.skipif('sys.version_info < (3, 8)')
@pytest.mark.parametrize("enable_pep563", (False, True))
def test_postponed_evaluation_of_annotations_pep563(enable_pep563):
Expand Down