cadquery-freecad-module/Libs/pyqode/python/backend/workers.py

323 lines
10 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Contains the worker classes/functions executed on the server side.
"""
import logging
import os
import tempfile
import jedi
from pyqode.core.share import Definition
from pyflakes import messages
def _logger():
"""
Returns the module's logger
"""
return logging.getLogger(__name__)
def calltips(request_data):
"""
Worker that returns a list of calltips.
A calltips is a tuple made of the following parts:
- module_name: name of the module of the function invoked
- call_name: name of the function that is being called
- params: the list of parameter names.
- index: index of the current parameter
- bracket_start
:returns tuple(module_name, call_name, params)
"""
code = request_data['code']
line = request_data['line'] + 1
column = request_data['column']
path = request_data['path']
# encoding = request_data['encoding']
encoding = 'utf-8'
# use jedi to get call signatures
script = jedi.Script(code, line, column, path, encoding)
signatures = script.call_signatures()
for sig in signatures:
results = (str(sig.module_name), str(sig.name),
[p.description for p in sig.params], sig.index,
sig.bracket_start, column)
# todo: add support for multiple signatures, for that we need a custom
# widget for showing calltips.
return results
return []
def goto_assignments(request_data):
"""
Go to assignements worker.
"""
code = request_data['code']
line = request_data['line'] + 1
column = request_data['column']
path = request_data['path']
# encoding = request_data['encoding']
encoding = 'utf-8'
script = jedi.Script(code, line, column, path, encoding)
try:
definitions = script.goto_assignments()
except jedi.NotFoundError:
pass
else:
ret_val = [(d.module_path, d.line - 1 if d.line else None,
d.column, d.full_name)
for d in definitions]
return ret_val
_old_definitions = {}
def _extract_def(d):
d_line, d_column = d.start_pos
# use full name for import type
if d.type == 'function':
try:
params = [p.name for p in d.params]
name = d.name + '(' + ', '.join(params) + ')'
except AttributeError:
name = d.name
else:
name = d.name
definition = Definition(name, d_line - 1, d_column,
icon_from_typename(d.name, d.type))
# check for methods in class or nested methods/classes
if d.type == "class" or d.type == 'function':
try:
sub_definitions = d.defined_names()
for sub_d in sub_definitions:
if (d.type == 'function' and sub_d.type == 'function') or \
d.type == 'class':
definition.add_child(_extract_def(sub_d))
except (AttributeError, IndexError):
pass
return definition
def defined_names(request_data):
"""
Returns the list of defined names for the document.
"""
global _old_definitions
ret_val = []
path = request_data['path']
toplvl_definitions = jedi.defined_names(
request_data['code'], path, 'utf-8')
for d in toplvl_definitions:
definition = _extract_def(d)
if d.type != 'import':
ret_val.append(definition)
_logger().debug("Document structure changed %s")
ret_val = [d.to_dict() for d in ret_val]
return ret_val
def quick_doc(request_data):
"""
Worker that returns the documentation of the symbol under cursor.
"""
code = request_data['code']
line = request_data['line'] + 1
column = request_data['column']
path = request_data['path']
# encoding = 'utf-8'
encoding = 'utf-8'
script = jedi.Script(code, line, column, path, encoding)
try:
definitions = script.goto_definitions()
except jedi.NotFoundError:
return []
else:
ret_val = [d.doc for d in definitions]
return ret_val
def run_pep8(request_data):
"""
Worker that run the pep8 tool on the current editor text.
:returns a list of tuples (msg, msg_type, line_number)
"""
import pep8
from pyqode.python.backend.pep8utils import CustomChecker
WARNING = 1
code = request_data['code']
path = request_data['path']
# setup our custom style guide with our custom checker which returns a list
# of strings instread of spitting the results at stdout
pep8style = pep8.StyleGuide(parse_argv=False, config_file='',
checker_class=CustomChecker)
try:
results = pep8style.input_file(path, lines=code.splitlines(True))
except Exception:
_logger().exception('Failed to run PEP8 analysis with data=%r'
% request_data)
return []
else:
messages = []
for line_number, offset, code, text, doc in results:
if code in ['W291', 'W292', 'W293', 'W391']:
continue
messages.append(('[PEP8] %s: %s' % (code, text), WARNING,
line_number - 1))
return messages
PYFLAKES_ERROR_MESSAGES = [
messages.DoctestSyntaxError,
messages.ReturnWithArgsInsideGenerator,
messages.UndefinedExport,
messages.UndefinedName,
messages.UndefinedLocal
]
def run_pyflakes(request_data):
"""
Worker that run a frosted (the fork of pyflakes) code analysis on the
current editor text.
"""
global prev_results
from pyflakes import checker
import _ast
WARNING = 1
ERROR = 2
ret_val = []
code = request_data['code']
path = request_data['path']
encoding = request_data['encoding']
if not encoding:
encoding = 'utf-8'
if not path:
path = os.path.join(tempfile.gettempdir(), 'temp.py')
if not code:
return []
else:
# First, compile into an AST and handle syntax errors.
try:
tree = compile(code.encode(encoding), path, "exec",
_ast.PyCF_ONLY_AST)
except SyntaxError as value:
msg = '[pyFlakes] %s' % value.args[0]
(lineno, offset, text) = value.lineno - 1, value.offset, value.text
# If there's an encoding problem with the file, the text is None
if text is None:
# Avoid using msg, since for the only known case, it
# contains a bogus message that claims the encoding the
# file declared was unknown.s
_logger().warning("[SyntaxError] %s: problem decoding source",
path)
else:
ret_val.append((msg, ERROR, lineno))
else:
# Okay, it's syntactically valid. Now check it.
w = checker.Checker(tree, os.path.split(path)[1])
w.messages.sort(key=lambda m: m.lineno)
for message in w.messages:
msg = "[pyFlakes] %s" % str(message).split(':')[-1].strip()
line = message.lineno - 1
status = WARNING \
if message.__class__ not in PYFLAKES_ERROR_MESSAGES \
else ERROR
ret_val.append((msg, status, line))
prev_results = ret_val
return ret_val
ICON_CLASS = ('code-class', ':/pyqode_python_icons/rc/class.png')
ICON_FUNC = ('code-function', ':/pyqode_python_icons/rc/func.png')
ICON_FUNC_PRIVATE = ('code-function', ':/pyqode_python_icons/rc/func_priv.png')
ICON_FUNC_PROTECTED = ('code-function',
':/pyqode_python_icons/rc/func_prot.png')
ICON_NAMESPACE = ('code-context', ':/pyqode_python_icons/rc/namespace.png')
ICON_VAR = ('code-variable', ':/pyqode_python_icons/rc/var.png')
ICON_KEYWORD = ('quickopen', ':/pyqode_python_icons/rc/keyword.png')
def icon_from_typename(name, icon_type):
"""
Returns the icon resource filename that corresponds to the given typename.
:param name: name of the completion. Use to make the distinction between
public and private completions (using the count of starting '_')
:pram typename: the typename reported by jedi
:returns: The associate icon resource filename or None.
"""
ICONS = {
'CLASS': ICON_CLASS,
'IMPORT': ICON_NAMESPACE,
'STATEMENT': ICON_VAR,
'FORFLOW': ICON_VAR,
'FORSTMT': ICON_VAR,
'WITHSTMT': ICON_VAR,
'GLOBALSTMT': ICON_VAR,
'MODULE': ICON_NAMESPACE,
'KEYWORD': ICON_KEYWORD,
'PARAM': ICON_VAR,
'ARRAY': ICON_VAR,
'INSTANCEELEMENT': ICON_VAR,
'INSTANCE': ICON_VAR,
'PARAM-PRIV': ICON_VAR,
'PARAM-PROT': ICON_VAR,
'FUNCTION': ICON_FUNC,
'DEF': ICON_FUNC,
'FUNCTION-PRIV': ICON_FUNC_PRIVATE,
'FUNCTION-PROT': ICON_FUNC_PROTECTED
}
ret_val = None
icon_type = icon_type.upper()
# jedi 0.8 introduced NamedPart class, which have a string instead of being
# one
if hasattr(name, "string"):
name = name.string
if icon_type == "FORFLOW" or icon_type == "STATEMENT":
icon_type = "PARAM"
if icon_type == "PARAM" or icon_type == "FUNCTION":
if name.startswith("__"):
icon_type += "-PRIV"
elif name.startswith("_"):
icon_type += "-PROT"
if icon_type in ICONS:
ret_val = ICONS[icon_type]
elif icon_type:
_logger().warning("Unimplemented completion icon_type: %s", icon_type)
return ret_val
class JediCompletionProvider:
"""
Provides code completion using the awesome `jedi`_ library
.. _`jedi`: https://github.com/davidhalter/jedi
"""
@staticmethod
def complete(code, line, column, path, encoding, prefix):
"""
Completes python code using `jedi`_.
:returns: a list of completion.
"""
ret_val = []
try:
script = jedi.Script(code, line + 1, column, path, encoding)
completions = script.completions()
print('completions: %r' % completions)
except jedi.NotFoundError:
completions = []
for completion in completions:
ret_val.append({
'name': completion.name,
'icon': icon_from_typename(
completion.name, completion.type),
'tooltip': completion.description})
return ret_val