├── .gitignore ├── multi_script_editor ├── __init__.py ├── helpText.txt ├── hqt.py ├── icons │ ├── __init__.py │ ├── clear.png │ ├── donate.png │ ├── exec_all.png │ ├── exec_sel.png │ ├── help.png │ ├── pw.png │ └── pw_logo.png ├── icons_rcs.py ├── jedi │ ├── __init__.py │ ├── __main__.py │ ├── _compatibility.py │ ├── api │ │ ├── __init__.py │ │ ├── classes.py │ │ ├── helpers.py │ │ ├── interpreter.py │ │ ├── keywords.py │ │ ├── replstartup.py │ │ └── usages.py │ ├── cache.py │ ├── common.py │ ├── debug.py │ ├── evaluate │ │ ├── __init__.py │ │ ├── analysis.py │ │ ├── cache.py │ │ ├── compiled │ │ │ ├── __init__.py │ │ │ ├── fake.py │ │ │ └── fake │ │ │ │ ├── _functools.pym │ │ │ │ ├── _sqlite3.pym │ │ │ │ ├── _sre.pym │ │ │ │ ├── _weakref.pym │ │ │ │ ├── builtins.pym │ │ │ │ ├── datetime.pym │ │ │ │ ├── io.pym │ │ │ │ └── posix.pym │ │ ├── docstrings.py │ │ ├── dynamic.py │ │ ├── finder.py │ │ ├── helpers.py │ │ ├── imports.py │ │ ├── iterable.py │ │ ├── param.py │ │ ├── precedence.py │ │ ├── recursion.py │ │ ├── representation.py │ │ ├── stdlib.py │ │ └── sys_path.py │ ├── parser │ │ ├── __init__.py │ │ ├── fast.py │ │ ├── representation.py │ │ ├── tokenize.py │ │ └── user_context.py │ ├── refactoring.py │ ├── settings.py │ └── utils.py ├── managers │ ├── _3dsmax.py │ ├── __init__.py │ ├── _houdini.py │ ├── _maya.py │ ├── _nuke.py │ ├── completeWidget.py │ ├── houdini │ │ ├── hou.py │ │ ├── multi_script_editor_16.pypanel │ │ ├── soptoolutils.py │ │ └── toolutils.py │ ├── nuke │ │ ├── __init__.py │ │ ├── callbacks.py │ │ ├── geo.py │ │ ├── main.py │ │ ├── math.py │ │ ├── nodes.py │ │ └── rotopaint.py │ └── run_3dsmax.py ├── run.cmd ├── run.sh ├── scriptEditor.py ├── sessionManager.py ├── settingsManager.py ├── shortcuts.txt ├── style │ ├── __init__.py │ ├── completer.qss │ ├── links.py │ ├── pw.ico │ ├── pw.png │ ├── script_editor.png │ └── style.css ├── tested.txt └── widgets │ ├── __init__.py │ ├── about.py │ ├── about.ui │ ├── about_UIs.py │ ├── completeWidget.py │ ├── findWidget.py │ ├── findWidget.ui │ ├── findWidget_UIs.py │ ├── inputWidget.py │ ├── numBarWidget.py │ ├── outputWidget.py │ ├── pythonSyntax │ ├── __init__.py │ ├── design.py │ ├── keywords.py │ └── syntaxHighLighter.py │ ├── scriptEditor.ui │ ├── scriptEditor_UIs.py │ ├── shortcuts.py │ ├── shortcuts.ui │ ├── shortcuts_UIs.py │ ├── tabWidget.py │ ├── themeEditor.py │ ├── themeEditor.ui │ ├── themeEditor_UI.py │ └── themeEditor_UIs.py ├── readme.md ├── readme_3dsmax.md ├── readme_houdini.md ├── readme_maya.md ├── readme_nuke.md ├── readme_standalone.md ├── releaseNote.md └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | todo 3 | .idea 4 | tmp 5 | _backup 6 | _tmp 7 | -------------------------------------------------------------------------------- /multi_script_editor/__init__.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | root = os.path.dirname(__file__) 4 | if not root in sys.path: 5 | sys.path.append(root) 6 | 7 | 8 | # HOUDINI 9 | def showHoudini(clear=False, ontop=False, name=None, floating=False, position=(), size=(), 10 | pane=None, replacePyPanel=False, hideTitleMenu=True): 11 | """ 12 | This method use hqt module. Download it before 13 | """ 14 | from .managers import _houdini 15 | reload(_houdini) 16 | _houdini.show(clear=clear, ontop=ontop, name=name, floating=floating, position=position, 17 | size=size, pane=pane, replacePyPanel=replacePyPanel, hideTitleMenu=hideTitleMenu) 18 | 19 | # NUKE 20 | def showNuke(panel=False): 21 | from .managers import _nuke 22 | reload(_nuke) 23 | _nuke.show(panel) 24 | 25 | 26 | # MAYA 27 | def showMaya(dock=False): 28 | from .managers import _maya 29 | reload (_maya) 30 | _maya.show(dock) 31 | 32 | # 3DSMAX PLUS 33 | def show3DSMax(): 34 | sys.argv = [] 35 | from .managers import _3dsmax 36 | reload (_3dsmax) 37 | _3dsmax.show() -------------------------------------------------------------------------------- /multi_script_editor/helpText.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

7 | Multi Script Editor v%s

8 |

9 | By PaulWinex paulwinex.com

10 |

11 | Editor Variables:

12 | 24 |


25 | 26 | -------------------------------------------------------------------------------- /multi_script_editor/icons/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | root = os.path.dirname(__file__) 4 | 5 | icons = dict( 6 | pw=os.path.join(root,'pw.png'), 7 | all=os.path.join(root,'exec_all.png'), 8 | sel=os.path.join(root,'exec_sel.png'), 9 | clear=os.path.join(root,'clear.png'), 10 | help=os.path.join(root,'help.png'), 11 | donate=os.path.join(root,'donate.png'), 12 | ) -------------------------------------------------------------------------------- /multi_script_editor/icons/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/clear.png -------------------------------------------------------------------------------- /multi_script_editor/icons/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/donate.png -------------------------------------------------------------------------------- /multi_script_editor/icons/exec_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/exec_all.png -------------------------------------------------------------------------------- /multi_script_editor/icons/exec_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/exec_sel.png -------------------------------------------------------------------------------- /multi_script_editor/icons/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/help.png -------------------------------------------------------------------------------- /multi_script_editor/icons/pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/pw.png -------------------------------------------------------------------------------- /multi_script_editor/icons/pw_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/icons/pw_logo.png -------------------------------------------------------------------------------- /multi_script_editor/jedi/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Jedi is an autocompletion tool for Python that can be used in IDEs/editors. 3 | Jedi works. Jedi is fast. It understands all of the basic Python syntax 4 | elements including many builtin functions. 5 | 6 | Additionaly, Jedi suports two different goto functions and has support for 7 | renaming as well as Pydoc support and some other IDE features. 8 | 9 | Jedi uses a very simple API to connect with IDE's. There's a reference 10 | implementation as a `VIM-Plugin `_, 11 | which uses Jedi's autocompletion. I encourage you to use Jedi in your IDEs. 12 | It's really easy. If there are any problems (also with licensing), just contact 13 | me. 14 | 15 | To give you a simple example how you can use the Jedi library, here is an 16 | example for the autocompletion feature: 17 | 18 | >>> import jedi 19 | >>> source = ''' 20 | ... import datetime 21 | ... datetime.da''' 22 | >>> script = jedi.Script(source, 3, len('datetime.da'), 'example.py') 23 | >>> script 24 | 25 | >>> completions = script.completions() 26 | >>> completions #doctest: +ELLIPSIS 27 | [, , ...] 28 | >>> print(completions[0].complete) 29 | te 30 | >>> print(completions[0].name) 31 | date 32 | 33 | As you see Jedi is pretty simple and allows you to concentrate on writing a 34 | good text editor, while still having very good IDE features for Python. 35 | """ 36 | 37 | __version__ = '0.8.1-final0' 38 | 39 | from jedi.api import Script, Interpreter, NotFoundError, set_debug_function 40 | from jedi.api import preload_module, defined_names 41 | from jedi import settings 42 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/__main__.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from os.path import join, dirname, abspath, isdir 3 | 4 | 5 | if len(argv) == 2 and argv[1] == 'repl': 6 | # don't want to use __main__ only for repl yet, maybe we want to use it for 7 | # something else. So just use the keyword ``repl`` for now. 8 | print(join(dirname(abspath(__file__)), 'api', 'replstartup.py')) 9 | elif len(argv) > 1 and argv[1] == 'linter': 10 | """ 11 | This is a pre-alpha API. You're not supposed to use it at all, except for 12 | testing. It will very likely change. 13 | """ 14 | import jedi 15 | import sys 16 | 17 | if '--debug' in sys.argv: 18 | jedi.set_debug_function() 19 | 20 | for path in sys.argv[2:]: 21 | if path.startswith('--'): 22 | continue 23 | if isdir(path): 24 | import fnmatch 25 | import os 26 | 27 | paths = [] 28 | for root, dirnames, filenames in os.walk(path): 29 | for filename in fnmatch.filter(filenames, '*.py'): 30 | paths.append(os.path.join(root, filename)) 31 | else: 32 | paths = [path] 33 | 34 | try: 35 | for path in paths: 36 | for error in jedi.Script(path=path)._analysis(): 37 | print(error) 38 | except Exception: 39 | if '--pdb' in sys.argv: 40 | import pdb 41 | pdb.post_mortem() 42 | else: 43 | raise 44 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/_compatibility.py: -------------------------------------------------------------------------------- 1 | """ 2 | To ensure compatibility from Python ``2.6`` - ``3.3``, a module has been 3 | created. Clearly there is huge need to use conforming syntax. 4 | """ 5 | import sys 6 | import imp 7 | import os 8 | import re 9 | try: 10 | import importlib 11 | except ImportError: 12 | pass 13 | 14 | is_py3 = sys.version_info[0] >= 3 15 | is_py33 = is_py3 and sys.version_info.minor >= 3 16 | is_py26 = not is_py3 and sys.version_info[1] < 7 17 | 18 | 19 | def find_module_py33(string, path=None): 20 | loader = importlib.machinery.PathFinder.find_module(string, path) 21 | 22 | if loader is None and path is None: # Fallback to find builtins 23 | loader = importlib.find_loader(string) 24 | 25 | if loader is None: 26 | raise ImportError("Couldn't find a loader for {0}".format(string)) 27 | 28 | try: 29 | is_package = loader.is_package(string) 30 | if is_package: 31 | module_path = os.path.dirname(loader.path) 32 | module_file = None 33 | else: 34 | module_path = loader.get_filename(string) 35 | module_file = open(module_path, 'rb') 36 | except AttributeError: 37 | # ExtensionLoader has not attribute get_filename, instead it has a 38 | # path attribute that we can use to retrieve the module path 39 | try: 40 | module_path = loader.path 41 | module_file = open(loader.path, 'rb') 42 | except AttributeError: 43 | module_path = string 44 | module_file = None 45 | finally: 46 | is_package = False 47 | 48 | return module_file, module_path, is_package 49 | 50 | 51 | def find_module_pre_py33(string, path=None): 52 | module_file, module_path, description = imp.find_module(string, path) 53 | module_type = description[2] 54 | return module_file, module_path, module_type is imp.PKG_DIRECTORY 55 | 56 | 57 | find_module = find_module_py33 if is_py33 else find_module_pre_py33 58 | find_module.__doc__ = """ 59 | Provides information about a module. 60 | 61 | This function isolates the differences in importing libraries introduced with 62 | python 3.3 on; it gets a module name and optionally a path. It will return a 63 | tuple containin an open file for the module (if not builtin), the filename 64 | or the name of the module if it is a builtin one and a boolean indicating 65 | if the module is contained in a package. 66 | """ 67 | 68 | # next was defined in python 2.6, in python 3 obj.next won't be possible 69 | # anymore 70 | try: 71 | next = next 72 | except NameError: 73 | _raiseStopIteration = object() 74 | 75 | def next(iterator, default=_raiseStopIteration): 76 | if not hasattr(iterator, 'next'): 77 | raise TypeError("not an iterator") 78 | try: 79 | return iterator.next() 80 | except StopIteration: 81 | if default is _raiseStopIteration: 82 | raise 83 | else: 84 | return default 85 | 86 | # unicode function 87 | try: 88 | unicode = unicode 89 | except NameError: 90 | unicode = str 91 | 92 | if is_py3: 93 | u = lambda s: s 94 | else: 95 | u = lambda s: s.decode('utf-8') 96 | 97 | u.__doc__ = """ 98 | Decode a raw string into unicode object. Do nothing in Python 3. 99 | """ 100 | 101 | # exec function 102 | if is_py3: 103 | def exec_function(source, global_map): 104 | exec(source, global_map) 105 | else: 106 | eval(compile("""def exec_function(source, global_map): 107 | exec source in global_map """, 'blub', 'exec')) 108 | 109 | # re-raise function 110 | if is_py3: 111 | def reraise(exception, traceback): 112 | raise exception.with_traceback(traceback) 113 | else: 114 | eval(compile(""" 115 | def reraise(exception, traceback): 116 | raise exception, None, traceback 117 | """, 'blub', 'exec')) 118 | 119 | reraise.__doc__ = """ 120 | Re-raise `exception` with a `traceback` object. 121 | 122 | Usage:: 123 | 124 | reraise(Exception, sys.exc_info()[2]) 125 | 126 | """ 127 | 128 | # hasattr function used because python 129 | if is_py3: 130 | hasattr = hasattr 131 | else: 132 | def hasattr(obj, name): 133 | try: 134 | getattr(obj, name) 135 | return True 136 | except AttributeError: 137 | return False 138 | 139 | 140 | class Python3Method(object): 141 | def __init__(self, func): 142 | self.func = func 143 | 144 | def __get__(self, obj, objtype): 145 | if obj is None: 146 | return lambda *args, **kwargs: self.func(*args, **kwargs) 147 | else: 148 | return lambda *args, **kwargs: self.func(obj, *args, **kwargs) 149 | 150 | 151 | def use_metaclass(meta, *bases): 152 | """ Create a class with a metaclass. """ 153 | if not bases: 154 | bases = (object,) 155 | return meta("HackClass", bases, {}) 156 | 157 | 158 | try: 159 | encoding = sys.stdout.encoding 160 | if encoding is None: 161 | encoding = 'utf-8' 162 | except AttributeError: 163 | encoding = 'ascii' 164 | 165 | 166 | def u(string): 167 | """Cast to unicode DAMMIT! 168 | Written because Python2 repr always implicitly casts to a string, so we 169 | have to cast back to a unicode (and we now that we always deal with valid 170 | unicode, because we check that in the beginning). 171 | """ 172 | if is_py3: 173 | return str(string) 174 | elif not isinstance(string, unicode): 175 | return unicode(str(string), 'UTF-8') 176 | return string 177 | 178 | try: 179 | import builtins # module name in python 3 180 | except ImportError: 181 | import __builtin__ as builtins 182 | 183 | 184 | import ast 185 | 186 | 187 | def literal_eval(string): 188 | # py3.0, py3.1 and py32 don't support unicode literals. Support those, I 189 | # don't want to write two versions of the tokenizer. 190 | if is_py3 and sys.version_info.minor < 3: 191 | if re.match('[uU][\'"]', string): 192 | string = string[1:] 193 | return ast.literal_eval(string) 194 | 195 | 196 | try: 197 | from itertools import zip_longest 198 | except ImportError: 199 | from itertools import izip_longest as zip_longest # Python 2 200 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/api/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers for the API 3 | """ 4 | import re 5 | 6 | from jedi.evaluate import imports 7 | 8 | 9 | def completion_parts(path_until_cursor): 10 | """ 11 | Returns the parts for the completion 12 | :return: tuple - (path, dot, like) 13 | """ 14 | match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path_until_cursor, flags=re.S) 15 | return match.groups() 16 | 17 | 18 | def sorted_definitions(defs): 19 | # Note: `or ''` below is required because `module_path` could be 20 | return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0)) 21 | 22 | 23 | def get_on_import_stmt(evaluator, user_context, user_stmt, is_like_search=False): 24 | """ 25 | Resolve the user statement, if it is an import. Only resolve the 26 | parts until the user position. 27 | """ 28 | import_names = user_stmt.get_all_import_names() 29 | kill_count = -1 30 | cur_name_part = None 31 | for i in import_names: 32 | if user_stmt.alias == i: 33 | continue 34 | for name_part in i.names: 35 | if name_part.end_pos >= user_context.position: 36 | if not cur_name_part: 37 | cur_name_part = name_part 38 | kill_count += 1 39 | 40 | context = user_context.get_context() 41 | just_from = next(context) == 'from' 42 | 43 | i = imports.ImportWrapper(evaluator, user_stmt, is_like_search, 44 | kill_count=kill_count, nested_resolve=True, 45 | is_just_from=just_from) 46 | return i, cur_name_part 47 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/api/interpreter.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import re 3 | 4 | from jedi._compatibility import builtins 5 | from jedi import debug 6 | from jedi.common import source_to_unicode 7 | from jedi.cache import underscore_memoization 8 | from jedi.evaluate import compiled 9 | from jedi.evaluate.compiled.fake import get_module 10 | from jedi.parser import representation as pr 11 | from jedi.parser.fast import FastParser 12 | from jedi.evaluate import helpers 13 | 14 | 15 | class InterpreterNamespace(pr.Module): 16 | def __init__(self, evaluator, namespace, parser_module): 17 | self.namespace = namespace 18 | self.parser_module = parser_module 19 | self._evaluator = evaluator 20 | 21 | @underscore_memoization 22 | def get_defined_names(self): 23 | for name in self.parser_module.get_defined_names(): 24 | yield name 25 | for key, value in self.namespace.items(): 26 | yield LazyName(self._evaluator, key, value) 27 | 28 | def scope_names_generator(self, position=None): 29 | yield self, list(self.get_defined_names()) 30 | 31 | def __getattr__(self, name): 32 | return getattr(self.parser_module, name) 33 | 34 | 35 | class LazyName(helpers.FakeName): 36 | def __init__(self, evaluator, name, value): 37 | super(LazyName, self).__init__(name) 38 | self._evaluator = evaluator 39 | self._value = value 40 | self._name = name 41 | 42 | @property 43 | @underscore_memoization 44 | def parent(self): 45 | obj = self._value 46 | parser_path = [] 47 | if inspect.ismodule(obj): 48 | module = obj 49 | else: 50 | class FakeParent(pr.Base): 51 | parent = None # To avoid having no parent for NamePart. 52 | path = None 53 | 54 | names = [] 55 | try: 56 | o = obj.__objclass__ 57 | names.append(obj.__name__) 58 | obj = o 59 | except AttributeError: 60 | pass 61 | 62 | try: 63 | module_name = obj.__module__ 64 | names.insert(0, obj.__name__) 65 | except AttributeError: 66 | # Unfortunately in some cases like `int` there's no __module__ 67 | module = builtins 68 | else: 69 | module = __import__(module_name) 70 | fake_name = helpers.FakeName(names, FakeParent()) 71 | parser_path = fake_name.names 72 | raw_module = get_module(self._value) 73 | 74 | try: 75 | path = module.__file__ 76 | except AttributeError: 77 | pass 78 | else: 79 | path = re.sub('c$', '', path) 80 | if path.endswith('.py'): 81 | # cut the `c` from `.pyc` 82 | with open(path) as f: 83 | source = source_to_unicode(f.read()) 84 | mod = FastParser(source, path[:-1]).module 85 | if not parser_path: 86 | return mod 87 | found = self._evaluator.eval_call_path(iter(parser_path), mod, None) 88 | if found: 89 | return found[0] 90 | debug.warning('Interpreter lookup for Python code failed %s', 91 | mod) 92 | 93 | module = compiled.CompiledObject(raw_module) 94 | if raw_module == builtins: 95 | # The builtins module is special and always cached. 96 | module = compiled.builtin 97 | return compiled.create(self._evaluator, self._value, module, module) 98 | 99 | @parent.setter 100 | def parent(self, value): 101 | """Needed because of the ``representation.Simple`` super class.""" 102 | 103 | 104 | def create(evaluator, namespace, parser_module): 105 | ns = InterpreterNamespace(evaluator, namespace, parser_module) 106 | for attr_name in pr.SCOPE_CONTENTS: 107 | for something in getattr(parser_module, attr_name): 108 | something.parent = ns 109 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/api/keywords.py: -------------------------------------------------------------------------------- 1 | import pydoc 2 | import keyword 3 | 4 | from jedi._compatibility import is_py3 5 | from jedi import common 6 | from jedi.evaluate import compiled 7 | 8 | try: 9 | from pydoc_data import topics as pydoc_topics 10 | except ImportError: 11 | # Python 2.6 12 | import pydoc_topics 13 | 14 | if is_py3: 15 | keys = keyword.kwlist 16 | else: 17 | keys = keyword.kwlist + ['None', 'False', 'True'] 18 | 19 | 20 | def keywords(string='', pos=(0, 0), all=False): 21 | if all: 22 | return set([Keyword(k, pos) for k in keys]) 23 | if string in keys: 24 | return set([Keyword(string, pos)]) 25 | return set() 26 | 27 | 28 | def keyword_names(*args, **kwargs): 29 | kwds = [] 30 | for k in keywords(*args, **kwargs): 31 | start = k.start_pos 32 | kwds.append(KeywordName(k, k.name, start)) 33 | return kwds 34 | 35 | 36 | def get_operator(string, pos): 37 | return Keyword(string, pos) 38 | 39 | 40 | class KeywordName(object): 41 | def __init__(self, parent, name, start_pos): 42 | self.parent = parent 43 | self.names = [name] 44 | self.start_pos = start_pos 45 | 46 | @property 47 | def end_pos(self): 48 | return self.start_pos[0], self.start_pos[1] + len(self.name) 49 | 50 | 51 | class Keyword(object): 52 | def __init__(self, name, pos): 53 | self.name = name 54 | self.start_pos = pos 55 | self.parent = compiled.builtin 56 | 57 | def get_parent_until(self): 58 | return self.parent 59 | 60 | @property 61 | def names(self): 62 | """ For a `parsing.Name` like comparision """ 63 | return [self.name] 64 | 65 | @property 66 | def docstr(self): 67 | return imitate_pydoc(self.name) 68 | 69 | def __repr__(self): 70 | return '<%s: %s>' % (type(self).__name__, self.name) 71 | 72 | 73 | def imitate_pydoc(string): 74 | """ 75 | It's not possible to get the pydoc's without starting the annoying pager 76 | stuff. 77 | """ 78 | # str needed because of possible unicode stuff in py2k (pydoc doesn't work 79 | # with unicode strings) 80 | string = str(string) 81 | h = pydoc.help 82 | with common.ignored(KeyError): 83 | # try to access symbols 84 | string = h.symbols[string] 85 | string, _, related = string.partition(' ') 86 | 87 | get_target = lambda s: h.topics.get(s, h.keywords.get(s)) 88 | while isinstance(string, str): 89 | string = get_target(string) 90 | 91 | try: 92 | # is a tuple now 93 | label, related = string 94 | except TypeError: 95 | return '' 96 | 97 | try: 98 | return pydoc_topics.topics[label] if pydoc_topics else '' 99 | except KeyError: 100 | return '' 101 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/api/replstartup.py: -------------------------------------------------------------------------------- 1 | """ 2 | To use Jedi completion in Python interpreter, add the following in your shell 3 | setup (e.g., ``.bashrc``):: 4 | 5 | export PYTHONSTARTUP="$(python -m jedi repl)" 6 | 7 | Then you will be able to use Jedi completer in your Python interpreter:: 8 | 9 | $ python 10 | Python 2.7.2+ (default, Jul 20 2012, 22:15:08) 11 | [GCC 4.6.1] on linux2 12 | Type "help", "copyright", "credits" or "license" for more information. 13 | >>> import os 14 | >>> os.path.join().split().in # doctest: +SKIP 15 | os.path.join().split().index os.path.join().split().insert 16 | 17 | """ 18 | import jedi.utils 19 | from jedi import __version__ as __jedi_version__ 20 | 21 | print('REPL completion using Jedi %s' % __jedi_version__) 22 | jedi.utils.setup_readline() 23 | 24 | del jedi 25 | 26 | # Note: try not to do many things here, as it will contaminate global 27 | # namespace of the interpreter. 28 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/api/usages.py: -------------------------------------------------------------------------------- 1 | from jedi._compatibility import u, unicode 2 | from jedi import common 3 | from jedi.api import classes 4 | from jedi.parser import representation as pr 5 | from jedi.evaluate import imports 6 | from jedi.evaluate import helpers 7 | 8 | 9 | def usages(evaluator, definitions, search_name, mods): 10 | def compare_array(definitions): 11 | """ `definitions` are being compared by module/start_pos, because 12 | sometimes the id's of the objects change (e.g. executions). 13 | """ 14 | result = [] 15 | for d in definitions: 16 | module = d.get_parent_until() 17 | result.append((module, d.start_pos)) 18 | return result 19 | 20 | def check_call_for_usage(call): 21 | stmt = call.parent 22 | while not isinstance(stmt.parent, pr.IsScope): 23 | stmt = stmt.parent 24 | # New definition, call cannot be a part of stmt 25 | if len(call.name) == 1 and call.execution is None \ 26 | and call.name in stmt.get_defined_names(): 27 | # Class params are not definitions (like function params). They 28 | # are super classes, that need to be resolved. 29 | if not (isinstance(stmt, pr.Param) and isinstance(stmt.parent, pr.Class)): 30 | return 31 | 32 | follow = [] # There might be multiple search_name's in one call_path 33 | call_path = list(call.generate_call_path()) 34 | for i, name in enumerate(call_path): 35 | # name is `pr.NamePart`. 36 | if u(name) == search_name: 37 | follow.append(call_path[:i + 1]) 38 | 39 | for call_path in follow: 40 | follow_res, search = evaluator.goto(call.parent, call_path) 41 | # names can change (getattr stuff), therefore filter names that 42 | # don't match `search`. 43 | 44 | # TODO add something like that in the future - for now usages are 45 | # completely broken anyway. 46 | #follow_res = [r for r in follow_res if str(r) == search] 47 | #print search.start_pos,search_name.start_pos 48 | #print follow_res, search, search_name, [(r, r.start_pos) for r in follow_res] 49 | follow_res = usages_add_import_modules(evaluator, follow_res, search) 50 | 51 | compare_follow_res = compare_array(follow_res) 52 | # compare to see if they match 53 | if any(r in compare_definitions for r in compare_follow_res): 54 | yield classes.Definition(evaluator, search) 55 | 56 | if not definitions: 57 | return set() 58 | 59 | compare_definitions = compare_array(definitions) 60 | mods |= set([d.get_parent_until() for d in definitions]) 61 | names = [] 62 | for m in imports.get_modules_containing_name(mods, search_name): 63 | try: 64 | stmts = m.used_names[search_name] 65 | except KeyError: 66 | continue 67 | for stmt in stmts: 68 | if isinstance(stmt, pr.Import): 69 | count = 0 70 | imps = [] 71 | for i in stmt.get_all_import_names(): 72 | for name_part in i.names: 73 | count += 1 74 | if unicode(name_part) == search_name: 75 | imps.append((count, name_part)) 76 | 77 | for used_count, name_part in imps: 78 | i = imports.ImportWrapper(evaluator, stmt, kill_count=count - used_count, 79 | nested_resolve=True) 80 | f = i.follow(is_goto=True) 81 | if set(f) & set(definitions): 82 | names.append(classes.Definition(evaluator, name_part)) 83 | else: 84 | for call in helpers.scan_statement_for_calls(stmt, search_name, assignment_details=True): 85 | names += check_call_for_usage(call) 86 | return names 87 | 88 | 89 | def usages_add_import_modules(evaluator, definitions, search_name): 90 | """ Adds the modules of the imports """ 91 | new = set() 92 | for d in definitions: 93 | if isinstance(d.parent, pr.Import): 94 | s = imports.ImportWrapper(evaluator, d.parent, nested_resolve=True) 95 | with common.ignored(IndexError): 96 | new.add(s.follow(is_goto=True)[0]) 97 | return set(definitions) | new 98 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/common.py: -------------------------------------------------------------------------------- 1 | """ A universal module with functions / classes without dependencies. """ 2 | import sys 3 | import contextlib 4 | import functools 5 | import re 6 | from ast import literal_eval 7 | 8 | from jedi._compatibility import unicode, next, reraise 9 | from jedi import settings 10 | 11 | 12 | class MultiLevelStopIteration(Exception): 13 | """ 14 | StopIteration's get catched pretty easy by for loops, let errors propagate. 15 | """ 16 | 17 | 18 | class UncaughtAttributeError(Exception): 19 | """ 20 | Important, because `__getattr__` and `hasattr` catch AttributeErrors 21 | implicitly. This is really evil (mainly because of `__getattr__`). 22 | `hasattr` in Python 2 is even more evil, because it catches ALL exceptions. 23 | Therefore this class originally had to be derived from `BaseException` 24 | instead of `Exception`. But because I removed relevant `hasattr` from 25 | the code base, we can now switch back to `Exception`. 26 | 27 | :param base: return values of sys.exc_info(). 28 | """ 29 | 30 | 31 | def safe_property(func): 32 | return property(reraise_uncaught(func)) 33 | 34 | 35 | def reraise_uncaught(func): 36 | """ 37 | Re-throw uncaught `AttributeError`. 38 | 39 | Usage: Put ``@rethrow_uncaught`` in front of the function 40 | which does **not** suppose to raise `AttributeError`. 41 | 42 | AttributeError is easily get caught by `hasattr` and another 43 | ``except AttributeError`` clause. This becomes problem when you use 44 | a lot of "dynamic" attributes (e.g., using ``@property``) because you 45 | can't distinguish if the property does not exist for real or some code 46 | inside of the "dynamic" attribute through that error. In a well 47 | written code, such error should not exist but getting there is very 48 | difficult. This decorator is to help us getting there by changing 49 | `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch. 50 | This helps us noticing bugs earlier and facilitates debugging. 51 | 52 | .. note:: Treating StopIteration here is easy. 53 | Add that feature when needed. 54 | """ 55 | @functools.wraps(func) 56 | def wrapper(*args, **kwds): 57 | try: 58 | return func(*args, **kwds) 59 | except AttributeError: 60 | exc_info = sys.exc_info() 61 | reraise(UncaughtAttributeError(exc_info[1]), exc_info[2]) 62 | return wrapper 63 | 64 | 65 | class PushBackIterator(object): 66 | def __init__(self, iterator): 67 | self.pushes = [] 68 | self.iterator = iterator 69 | self.current = None 70 | 71 | def push_back(self, value): 72 | self.pushes.append(value) 73 | 74 | def __iter__(self): 75 | return self 76 | 77 | def next(self): 78 | """ Python 2 Compatibility """ 79 | return self.__next__() 80 | 81 | def __next__(self): 82 | if self.pushes: 83 | self.current = self.pushes.pop() 84 | else: 85 | self.current = next(self.iterator) 86 | return self.current 87 | 88 | 89 | @contextlib.contextmanager 90 | def scale_speed_settings(factor): 91 | a = settings.max_executions 92 | b = settings.max_until_execution_unique 93 | settings.max_executions *= factor 94 | settings.max_until_execution_unique *= factor 95 | yield 96 | settings.max_executions = a 97 | settings.max_until_execution_unique = b 98 | 99 | 100 | def indent_block(text, indention=' '): 101 | """This function indents a text block with a default of four spaces.""" 102 | temp = '' 103 | while text and text[-1] == '\n': 104 | temp += text[-1] 105 | text = text[:-1] 106 | lines = text.split('\n') 107 | return '\n'.join(map(lambda s: indention + s, lines)) + temp 108 | 109 | 110 | @contextlib.contextmanager 111 | def ignored(*exceptions): 112 | """ 113 | Context manager that ignores all of the specified exceptions. This will 114 | be in the standard library starting with Python 3.4. 115 | """ 116 | try: 117 | yield 118 | except exceptions: 119 | pass 120 | 121 | 122 | def source_to_unicode(source, encoding=None): 123 | def detect_encoding(): 124 | """ 125 | For the implementation of encoding definitions in Python, look at: 126 | - http://www.python.org/dev/peps/pep-0263/ 127 | - http://docs.python.org/2/reference/lexical_analysis.html#encoding-declarations 128 | """ 129 | byte_mark = literal_eval(r"b'\xef\xbb\xbf'") 130 | if source.startswith(byte_mark): 131 | # UTF-8 byte-order mark 132 | return 'utf-8' 133 | 134 | first_two_lines = re.match(r'(?:[^\n]*\n){0,2}', str(source)).group(0) 135 | possible_encoding = re.search(r"coding[=:]\s*([-\w.]+)", 136 | first_two_lines) 137 | if possible_encoding: 138 | return possible_encoding.group(1) 139 | else: 140 | # the default if nothing else has been set -> PEP 263 141 | return encoding if encoding is not None else 'iso-8859-1' 142 | 143 | if isinstance(source, unicode): 144 | # only cast str/bytes 145 | return source 146 | 147 | # cast to unicode by default 148 | return unicode(source, detect_encoding(), 'replace') 149 | 150 | 151 | def splitlines(string): 152 | """ 153 | A splitlines for Python code. In contrast to Python's ``str.splitlines``, 154 | looks at form feeds and other special characters as normal text. Just 155 | splits ``\n`` and ``\r\n``. 156 | Also different: Returns ``['']`` for an empty string input. 157 | """ 158 | return re.split('\n|\r\n', string) 159 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/debug.py: -------------------------------------------------------------------------------- 1 | from jedi._compatibility import encoding, is_py3 2 | import inspect 3 | import os 4 | import time 5 | 6 | try: 7 | if os.name == 'nt': 8 | # does not work on Windows, as pyreadline and colorama interfere 9 | raise ImportError 10 | else: 11 | # Use colorama for nicer console output. 12 | from colorama import Fore, init 13 | from colorama import initialise 14 | # pytest resets the stream at the end - causes troubles. Since after 15 | # every output the stream is reset automatically we don't need this. 16 | initialise.atexit_done = True 17 | init() 18 | except ImportError: 19 | class Fore(object): 20 | RED = '' 21 | GREEN = '' 22 | YELLOW = '' 23 | RESET = '' 24 | 25 | NOTICE = object() 26 | WARNING = object() 27 | SPEED = object() 28 | 29 | enable_speed = False 30 | enable_warning = False 31 | enable_notice = False 32 | 33 | # callback, interface: level, str 34 | debug_function = None 35 | ignored_modules = ['jedi.evaluate.builtin', 'jedi.parser'] 36 | _debug_indent = -1 37 | _start_time = time.time() 38 | 39 | 40 | def reset_time(): 41 | global _start_time, _debug_indent 42 | _start_time = time.time() 43 | _debug_indent = -1 44 | 45 | 46 | def increase_indent(func): 47 | """Decorator for makin """ 48 | def wrapper(*args, **kwargs): 49 | global _debug_indent 50 | _debug_indent += 1 51 | result = func(*args, **kwargs) 52 | _debug_indent -= 1 53 | return result 54 | return wrapper 55 | 56 | 57 | def dbg(message, *args): 58 | """ Looks at the stack, to see if a debug message should be printed. """ 59 | if debug_function and enable_notice: 60 | frm = inspect.stack()[1] 61 | mod = inspect.getmodule(frm[0]) 62 | if not (mod.__name__ in ignored_modules): 63 | i = ' ' * _debug_indent 64 | debug_function(NOTICE, i + 'dbg: ' + message % args) 65 | 66 | 67 | def warning(message, *args): 68 | if debug_function and enable_warning: 69 | i = ' ' * _debug_indent 70 | debug_function(WARNING, i + 'warning: ' + message % args) 71 | 72 | 73 | def speed(name): 74 | if debug_function and enable_speed: 75 | now = time.time() 76 | i = ' ' * _debug_indent 77 | debug_function(SPEED, i + 'speed: ' + '%s %s' % (name, now - _start_time)) 78 | 79 | 80 | def print_to_stdout(level, str_out): 81 | """ The default debug function """ 82 | if level == NOTICE: 83 | col = Fore.GREEN 84 | elif level == WARNING: 85 | col = Fore.RED 86 | else: 87 | col = Fore.YELLOW 88 | if not is_py3: 89 | str_out = str_out.encode(encoding, 'replace') 90 | print(col + str_out + Fore.RESET) 91 | 92 | 93 | # debug_function = print_to_stdout 94 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/cache.py: -------------------------------------------------------------------------------- 1 | """ 2 | - the popular ``memoize_default`` works like a typical memoize and returns the 3 | default otherwise. 4 | - ``CachedMetaClass`` uses ``memoize_default`` to do the same with classes. 5 | """ 6 | 7 | NO_DEFAULT = object() 8 | 9 | 10 | def memoize_default(default=None, evaluator_is_first_arg=False, second_arg_is_evaluator=False): 11 | """ This is a typical memoization decorator, BUT there is one difference: 12 | To prevent recursion it sets defaults. 13 | 14 | Preventing recursion is in this case the much bigger use than speed. I 15 | don't think, that there is a big speed difference, but there are many cases 16 | where recursion could happen (think about a = b; b = a). 17 | """ 18 | def func(function): 19 | def wrapper(obj, *args, **kwargs): 20 | if evaluator_is_first_arg: 21 | cache = obj.memoize_cache 22 | elif second_arg_is_evaluator: # needed for meta classes 23 | cache = args[0].memoize_cache 24 | else: 25 | cache = obj._evaluator.memoize_cache 26 | 27 | try: 28 | memo = cache[function] 29 | except KeyError: 30 | memo = {} 31 | cache[function] = memo 32 | 33 | key = (obj, args, frozenset(kwargs.items())) 34 | if key in memo: 35 | return memo[key] 36 | else: 37 | if default is not NO_DEFAULT: 38 | memo[key] = default 39 | rv = function(obj, *args, **kwargs) 40 | memo[key] = rv 41 | return rv 42 | return wrapper 43 | return func 44 | 45 | 46 | class CachedMetaClass(type): 47 | """ 48 | This is basically almost the same than the decorator above, it just caches 49 | class initializations. I haven't found any other way, so I'm doing it with 50 | meta classes. 51 | """ 52 | @memoize_default(None, second_arg_is_evaluator=True) 53 | def __call__(self, *args, **kwargs): 54 | return super(CachedMetaClass, self).__call__(*args, **kwargs) 55 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake.py: -------------------------------------------------------------------------------- 1 | """ 2 | Loads functions that are mixed in to the standard library. E.g. builtins are 3 | written in C (binaries), but my autocompletion only understands Python code. By 4 | mixing in Python code, the autocompletion should work much better for builtins. 5 | """ 6 | 7 | import os 8 | import inspect 9 | 10 | from jedi._compatibility import is_py3, builtins, unicode 11 | from jedi.parser import Parser 12 | from jedi.parser import tokenize 13 | from jedi.parser.representation import Class 14 | from jedi.evaluate.helpers import FakeName 15 | 16 | modules = {} 17 | 18 | 19 | def _load_faked_module(module): 20 | module_name = module.__name__ 21 | if module_name == '__builtin__' and not is_py3: 22 | module_name = 'builtins' 23 | 24 | try: 25 | return modules[module_name] 26 | except KeyError: 27 | path = os.path.dirname(os.path.abspath(__file__)) 28 | try: 29 | with open(os.path.join(path, 'fake', module_name) + '.pym') as f: 30 | source = f.read() 31 | except IOError: 32 | modules[module_name] = None 33 | return 34 | module = Parser(unicode(source), module_name).module 35 | modules[module_name] = module 36 | 37 | if module_name == 'builtins' and not is_py3: 38 | # There are two implementations of `open` for either python 2/3. 39 | # -> Rename the python2 version (`look at fake/builtins.pym`). 40 | open_func = search_scope(module, 'open') 41 | open_func.name = FakeName('open_python3') 42 | open_func = search_scope(module, 'open_python2') 43 | open_func.name = FakeName('open') 44 | return module 45 | 46 | 47 | def search_scope(scope, obj_name): 48 | for s in scope.subscopes: 49 | if str(s.name) == obj_name: 50 | return s 51 | 52 | 53 | def get_module(obj): 54 | if inspect.ismodule(obj): 55 | return obj 56 | try: 57 | obj = obj.__objclass__ 58 | except AttributeError: 59 | pass 60 | 61 | try: 62 | imp_plz = obj.__module__ 63 | except AttributeError: 64 | # Unfortunately in some cases like `int` there's no __module__ 65 | return builtins 66 | else: 67 | return __import__(imp_plz) 68 | 69 | 70 | def _faked(module, obj, name): 71 | # Crazy underscore actions to try to escape all the internal madness. 72 | if module is None: 73 | module = get_module(obj) 74 | 75 | faked_mod = _load_faked_module(module) 76 | if faked_mod is None: 77 | return 78 | 79 | # Having the module as a `parser.representation.module`, we need to scan 80 | # for methods. 81 | if name is None: 82 | if inspect.isbuiltin(obj): 83 | return search_scope(faked_mod, obj.__name__) 84 | elif not inspect.isclass(obj): 85 | # object is a method or descriptor 86 | cls = search_scope(faked_mod, obj.__objclass__.__name__) 87 | if cls is None: 88 | return 89 | return search_scope(cls, obj.__name__) 90 | else: 91 | if obj == module: 92 | return search_scope(faked_mod, name) 93 | else: 94 | cls = search_scope(faked_mod, obj.__name__) 95 | if cls is None: 96 | return 97 | return search_scope(cls, name) 98 | 99 | 100 | def get_faked(module, obj, name=None): 101 | obj = obj.__class__ if is_class_instance(obj) else obj 102 | result = _faked(module, obj, name) 103 | if not isinstance(result, Class) and result is not None: 104 | # Set the docstr which was previously not set (faked modules don't 105 | # contain it). 106 | doc = '''"""%s"""''' % obj.__doc__ # TODO need escapes. 107 | result.add_docstr(tokenize.Token(tokenize.STRING, doc, (0, 0))) 108 | return result 109 | 110 | 111 | def is_class_instance(obj): 112 | """Like inspect.* methods.""" 113 | return not (inspect.isclass(obj) or inspect.ismodule(obj) 114 | or inspect.isbuiltin(obj) or inspect.ismethod(obj) 115 | or inspect.ismethoddescriptor(obj) or inspect.iscode(obj) 116 | or inspect.isgenerator(obj)) 117 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/_functools.pym: -------------------------------------------------------------------------------- 1 | class partial(): 2 | def __init__(self, func, *args, **keywords): 3 | self.__func = func 4 | self.__args = args 5 | self.__keywords = keywords 6 | 7 | def __call__(self, *args, **kwargs): 8 | # I know this doesn't work in Python, but in Jedi it does ;-) 9 | return self.__func(*self.__args, *args, **self.keywords, **kwargs) 10 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/_sqlite3.pym: -------------------------------------------------------------------------------- 1 | def connect(database, timeout=None, isolation_level=None, detect_types=None, factory=None): 2 | return Connection() 3 | 4 | 5 | class Connection(): 6 | def cursor(self): 7 | return Cursor() 8 | 9 | 10 | class Cursor(): 11 | def cursor(self): 12 | return Cursor() 13 | 14 | def fetchone(self): 15 | return Row() 16 | 17 | def fetchmany(self, size=cursor.arraysize): 18 | return [self.fetchone()] 19 | 20 | def fetchall(self): 21 | return [self.fetchone()] 22 | 23 | 24 | class Row(): 25 | def keys(self): 26 | return [''] 27 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/_sre.pym: -------------------------------------------------------------------------------- 1 | def compile(): 2 | class SRE_Match(): 3 | endpos = int() 4 | lastgroup = int() 5 | lastindex = int() 6 | pos = int() 7 | string = str() 8 | regs = ((int(), int()),) 9 | 10 | def __init__(self, pattern): 11 | self.re = pattern 12 | 13 | def start(self): 14 | return int() 15 | 16 | def end(self): 17 | return int() 18 | 19 | def span(self): 20 | return int(), int() 21 | 22 | def expand(self): 23 | return str() 24 | 25 | def group(self, nr): 26 | return str() 27 | 28 | def groupdict(self): 29 | return {str(): str()} 30 | 31 | def groups(self): 32 | return (str(),) 33 | 34 | class SRE_Pattern(): 35 | flags = int() 36 | groupindex = {} 37 | groups = int() 38 | pattern = str() 39 | 40 | def findall(self, string, pos=None, endpos=None): 41 | """ 42 | findall(string[, pos[, endpos]]) --> list. 43 | Return a list of all non-overlapping matches of pattern in string. 44 | """ 45 | return [str()] 46 | 47 | def finditer(self, string, pos=None, endpos=None): 48 | """ 49 | finditer(string[, pos[, endpos]]) --> iterator. 50 | Return an iterator over all non-overlapping matches for the 51 | RE pattern in string. For each match, the iterator returns a 52 | match object. 53 | """ 54 | yield SRE_Match(self) 55 | 56 | def match(self, string, pos=None, endpos=None): 57 | """ 58 | match(string[, pos[, endpos]]) --> match object or None. 59 | Matches zero or more characters at the beginning of the string 60 | pattern 61 | """ 62 | return SRE_Match(self) 63 | 64 | def scanner(self, string, pos=None, endpos=None): 65 | pass 66 | 67 | def search(self, string, pos=None, endpos=None): 68 | """ 69 | search(string[, pos[, endpos]]) --> match object or None. 70 | Scan through string looking for a match, and return a corresponding 71 | MatchObject instance. Return None if no position in the string matches. 72 | """ 73 | return SRE_Match(self) 74 | 75 | def split(self, string, maxsplit=0]): 76 | """ 77 | split(string[, maxsplit = 0]) --> list. 78 | Split string by the occurrences of pattern. 79 | """ 80 | return [str()] 81 | 82 | def sub(self, repl, string, count=0): 83 | """ 84 | sub(repl, string[, count = 0]) --> newstring 85 | Return the string obtained by replacing the leftmost non-overlapping 86 | occurrences of pattern in string by the replacement repl. 87 | """ 88 | return str() 89 | 90 | def subn(self, repl, string, count=0): 91 | """ 92 | subn(repl, string[, count = 0]) --> (newstring, number of subs) 93 | Return the tuple (new_string, number_of_subs_made) found by replacing 94 | the leftmost non-overlapping occurrences of pattern with the 95 | replacement repl. 96 | """ 97 | return (str(), int()) 98 | 99 | return SRE_Pattern() 100 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/_weakref.pym: -------------------------------------------------------------------------------- 1 | def proxy(object, callback=None): 2 | return object 3 | 4 | class weakref(): 5 | def __init__(self, object, callback=None): 6 | self.__object = object 7 | def __call__(self): 8 | return self.__object 9 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/builtins.pym: -------------------------------------------------------------------------------- 1 | """ 2 | Pure Python implementation of some builtins. 3 | This code is not going to be executed anywhere. 4 | These implementations are not always correct, but should work as good as 5 | possible for the auto completion. 6 | """ 7 | 8 | 9 | def next(iterator, default=None): 10 | if hasattr("next"): 11 | return iterator.next() 12 | else: 13 | return iterator.__next__() 14 | return default 15 | 16 | 17 | def iter(collection, sentinel=None): 18 | if sentinel: 19 | yield collection() 20 | else: 21 | for c in collection: 22 | yield c 23 | 24 | 25 | def range(start, stop=None, step=1): 26 | return [0] 27 | 28 | 29 | class file(): 30 | def __iter__(self): 31 | yield '' 32 | def next(self): 33 | return '' 34 | 35 | 36 | class xrange(): 37 | # Attention: this function doesn't exist in Py3k (there it is range). 38 | def __iter__(self): 39 | yield 1 40 | 41 | def count(self): 42 | return 1 43 | 44 | def index(self): 45 | return 1 46 | 47 | 48 | def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True): 49 | import io 50 | return io.TextIOWrapper(file, mode, buffering, encoding, errors, newline, closefd) 51 | 52 | 53 | def open_python2(name, mode=None, buffering=None): 54 | return file(name, mode, buffering) 55 | 56 | 57 | #-------------------------------------------------------- 58 | # descriptors 59 | #-------------------------------------------------------- 60 | class property(): 61 | def __init__(self, fget, fset=None, fdel=None, doc=None): 62 | self.fget = fget 63 | self.fset = fset 64 | self.fdel = fdel 65 | self.__doc__ = doc 66 | 67 | def __get__(self, obj, cls): 68 | return self.fget(obj) 69 | 70 | def __set__(self, obj, value): 71 | self.fset(obj, value) 72 | 73 | def __delete__(self, obj): 74 | self.fdel(obj) 75 | 76 | def setter(self, func): 77 | self.fset = func 78 | return self 79 | 80 | def getter(self, func): 81 | self.fget = func 82 | return self 83 | 84 | def deleter(self, func): 85 | self.fdel = func 86 | return self 87 | 88 | 89 | class staticmethod(): 90 | def __init__(self, func): 91 | self.__func = func 92 | 93 | def __get__(self, obj, cls): 94 | return self.__func 95 | 96 | 97 | class classmethod(): 98 | def __init__(self, func): 99 | self.__func = func 100 | 101 | def __get__(self, obj, cls): 102 | def _method(*args, **kwargs): 103 | return self.__func(cls, *args, **kwargs) 104 | return _method 105 | 106 | 107 | #-------------------------------------------------------- 108 | # array stuff 109 | #-------------------------------------------------------- 110 | class list(): 111 | def __init__(self, iterable=[]): 112 | self.__iterable = [] 113 | for i in iterable: 114 | self.__iterable += [i] 115 | 116 | def __iter__(self): 117 | for i in self.__iterable: 118 | yield i 119 | 120 | def __getitem__(self, y): 121 | return self.__iterable[y] 122 | 123 | def pop(self): 124 | return self.__iterable[-1] 125 | 126 | 127 | class tuple(): 128 | def __init__(self, iterable=[]): 129 | self.__iterable = [] 130 | for i in iterable: 131 | self.__iterable += [i] 132 | 133 | def __iter__(self): 134 | for i in self.__iterable: 135 | yield i 136 | 137 | def __getitem__(self, y): 138 | return self.__iterable[y] 139 | 140 | def index(self): 141 | return 1 142 | 143 | def count(self): 144 | return 1 145 | 146 | 147 | class set(): 148 | def __init__(self, iterable=[]): 149 | self.__iterable = iterable 150 | 151 | def __iter__(self): 152 | for i in self.__iterable: 153 | yield i 154 | 155 | def pop(self): 156 | return self.__iterable.pop() 157 | 158 | def copy(self): 159 | return self 160 | 161 | def difference(self, other): 162 | return self - other 163 | 164 | def intersection(self, other): 165 | return self & other 166 | 167 | def symmetric_difference(self, other): 168 | return self ^ other 169 | 170 | def union(self, other): 171 | return self | other 172 | 173 | 174 | class frozenset(): 175 | def __init__(self, iterable=[]): 176 | self.__iterable = iterable 177 | 178 | def __iter__(self): 179 | for i in self.__iterable: 180 | yield i 181 | 182 | def copy(self): 183 | return self 184 | 185 | 186 | class dict(): 187 | def __init__(self, **elements): 188 | self.__elements = elements 189 | 190 | def clear(self): 191 | # has a strange docstr 192 | pass 193 | 194 | def get(self, k, d=None): 195 | # TODO implement 196 | try: 197 | #return self.__elements[k] 198 | pass 199 | except KeyError: 200 | return d 201 | 202 | def setdefault(self, k, d): 203 | # TODO maybe also return the content 204 | return d 205 | 206 | 207 | class reversed(): 208 | def __init__(self, sequence): 209 | self.__sequence = sequence 210 | 211 | def __iter__(self): 212 | for i in self.__sequence: 213 | yield i 214 | 215 | def __next__(self): 216 | return next(self.__iter__()) 217 | 218 | def next(self): 219 | return next(self.__iter__()) 220 | 221 | 222 | def sorted(iterable, cmp=None, key=None, reverse=False): 223 | return iterable 224 | 225 | 226 | #-------------------------------------------------------- 227 | # basic types 228 | #-------------------------------------------------------- 229 | class int(): 230 | def __init__(self, x, base=None): 231 | pass 232 | 233 | 234 | class str(): 235 | def __init__(self, obj): 236 | pass 237 | 238 | 239 | class type(): 240 | def mro(): 241 | return [object] 242 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/datetime.pym: -------------------------------------------------------------------------------- 1 | class datetime(): 2 | @staticmethod 3 | def now(): 4 | return datetime() 5 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/io.pym: -------------------------------------------------------------------------------- 1 | class TextIOWrapper(): 2 | def __next__(self): 3 | return 'hacked io return' 4 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/compiled/fake/posix.pym: -------------------------------------------------------------------------------- 1 | def getcwd(): 2 | return '' 3 | 4 | def getcwdu(): 5 | return '' 6 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/docstrings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Docstrings are another source of information for functions and classes. 3 | :mod:`jedi.evaluate.dynamic` tries to find all executions of functions, while 4 | the docstring parsing is much easier. There are two different types of 5 | docstrings that |jedi| understands: 6 | 7 | - `Sphinx `_ 8 | - `Epydoc `_ 9 | 10 | For example, the sphinx annotation ``:type foo: str`` clearly states that the 11 | type of ``foo`` is ``str``. 12 | 13 | As an addition to parameter searching, this module also provides return 14 | annotations. 15 | """ 16 | 17 | import re 18 | from itertools import chain 19 | from textwrap import dedent 20 | 21 | from jedi.evaluate.cache import memoize_default 22 | from jedi.parser import Parser 23 | from jedi.common import indent_block 24 | 25 | DOCSTRING_PARAM_PATTERNS = [ 26 | r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx 27 | r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc 28 | ] 29 | 30 | DOCSTRING_RETURN_PATTERNS = [ 31 | re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx 32 | re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epydoc 33 | ] 34 | 35 | REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') 36 | 37 | 38 | @memoize_default(None, evaluator_is_first_arg=True) 39 | def follow_param(evaluator, param): 40 | func = param.parent_function 41 | param_str = _search_param_in_docstr(func.raw_doc, str(param.get_name())) 42 | return _evaluate_for_statement_string(evaluator, param_str, param.get_parent_until()) 43 | 44 | 45 | def _search_param_in_docstr(docstr, param_str): 46 | """ 47 | Search `docstr` for a type of `param_str`. 48 | 49 | >>> _search_param_in_docstr(':type param: int', 'param') 50 | 'int' 51 | >>> _search_param_in_docstr('@type param: int', 'param') 52 | 'int' 53 | >>> _search_param_in_docstr( 54 | ... ':type param: :class:`threading.Thread`', 'param') 55 | 'threading.Thread' 56 | >>> _search_param_in_docstr('no document', 'param') is None 57 | True 58 | 59 | """ 60 | # look at #40 to see definitions of those params 61 | patterns = [re.compile(p % re.escape(param_str)) 62 | for p in DOCSTRING_PARAM_PATTERNS] 63 | for pattern in patterns: 64 | match = pattern.search(docstr) 65 | if match: 66 | return _strip_rst_role(match.group(1)) 67 | 68 | return None 69 | 70 | 71 | def _strip_rst_role(type_str): 72 | """ 73 | Strip off the part looks like a ReST role in `type_str`. 74 | 75 | >>> _strip_rst_role(':class:`ClassName`') # strip off :class: 76 | 'ClassName' 77 | >>> _strip_rst_role(':py:obj:`module.Object`') # works with domain 78 | 'module.Object' 79 | >>> _strip_rst_role('ClassName') # do nothing when not ReST role 80 | 'ClassName' 81 | 82 | See also: 83 | http://sphinx-doc.org/domains.html#cross-referencing-python-objects 84 | 85 | """ 86 | match = REST_ROLE_PATTERN.match(type_str) 87 | if match: 88 | return match.group(1) 89 | else: 90 | return type_str 91 | 92 | 93 | def _evaluate_for_statement_string(evaluator, string, module): 94 | code = dedent(""" 95 | def pseudo_docstring_stuff(): 96 | '''Create a pseudo function for docstring statements.''' 97 | %s 98 | """) 99 | if string is None: 100 | return [] 101 | 102 | for element in re.findall('((?:\w+\.)*\w+)\.', string): 103 | # Try to import module part in dotted name. 104 | # (e.g., 'threading' in 'threading.Thread'). 105 | string = 'import %s\n' % element + string 106 | 107 | p = Parser(code % indent_block(string), no_docstr=True) 108 | pseudo_cls = p.module.subscopes[0] 109 | try: 110 | stmt = pseudo_cls.statements[-1] 111 | except IndexError: 112 | return [] 113 | 114 | # Use the module of the param. 115 | # TODO this module is not the module of the param in case of a function 116 | # call. In that case it's the module of the function call. 117 | # stuffed with content from a function call. 118 | pseudo_cls.parent = module 119 | definitions = evaluator.eval_statement(stmt) 120 | it = (evaluator.execute(d) for d in definitions) 121 | # TODO Executing tuples does not make sense, people tend to say 122 | # `(str, int)` in a type annotation, which means that it returns a tuple 123 | # with both types. 124 | # At this point we just return the classes if executing wasn't possible, 125 | # i.e. is a tuple. 126 | return list(chain.from_iterable(it)) or definitions 127 | 128 | 129 | @memoize_default(None, evaluator_is_first_arg=True) 130 | def find_return_types(evaluator, func): 131 | def search_return_in_docstr(code): 132 | for p in DOCSTRING_RETURN_PATTERNS: 133 | match = p.search(code) 134 | if match: 135 | return _strip_rst_role(match.group(1)) 136 | 137 | type_str = search_return_in_docstr(func.raw_doc) 138 | return _evaluate_for_statement_string(evaluator, type_str, func.get_parent_until()) 139 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/dynamic.py: -------------------------------------------------------------------------------- 1 | """ 2 | One of the really important features of |jedi| is to have an option to 3 | understand code like this:: 4 | 5 | def foo(bar): 6 | bar. # completion here 7 | foo(1) 8 | 9 | There's no doubt wheter bar is an ``int`` or not, but if there's also a call 10 | like ``foo('str')``, what would happen? Well, we'll just show both. Because 11 | that's what a human would expect. 12 | 13 | It works as follows: 14 | 15 | - |Jedi| sees a param 16 | - search for function calls named ``foo`` 17 | - execute these calls and check the input. This work with a ``ParamListener``. 18 | """ 19 | 20 | from jedi._compatibility import unicode 21 | from jedi.parser import representation as pr 22 | from jedi import settings 23 | from jedi.evaluate import helpers 24 | from jedi.evaluate.cache import memoize_default 25 | from jedi.evaluate import imports 26 | 27 | # This is something like the sys.path, but only for searching params. It means 28 | # that this is the order in which Jedi searches params. 29 | search_param_modules = ['.'] 30 | 31 | 32 | class ParamListener(object): 33 | """ 34 | This listener is used to get the params for a function. 35 | """ 36 | def __init__(self): 37 | self.param_possibilities = [] 38 | 39 | def execute(self, params): 40 | self.param_possibilities.append(params) 41 | 42 | 43 | @memoize_default([], evaluator_is_first_arg=True) 44 | def search_params(evaluator, param): 45 | """ 46 | This is a dynamic search for params. If you try to complete a type: 47 | 48 | >>> def func(foo): 49 | ... foo 50 | >>> func(1) 51 | >>> func("") 52 | 53 | It is not known what the type is, because it cannot be guessed with 54 | recursive madness. Therefore one has to analyse the statements that are 55 | calling the function, as well as analyzing the incoming params. 56 | """ 57 | if not settings.dynamic_params: 58 | return [] 59 | 60 | def get_params_for_module(module): 61 | """ 62 | Returns the values of a param, or an empty array. 63 | """ 64 | @memoize_default([], evaluator_is_first_arg=True) 65 | def get_posibilities(evaluator, module, func_name): 66 | try: 67 | possible_stmts = module.used_names[func_name] 68 | except KeyError: 69 | return [] 70 | 71 | for stmt in possible_stmts: 72 | if isinstance(stmt, pr.Import): 73 | continue 74 | calls = helpers.scan_statement_for_calls(stmt, func_name) 75 | for c in calls: 76 | # no execution means that params cannot be set 77 | call_path = list(c.generate_call_path()) 78 | pos = c.start_pos 79 | scope = stmt.parent 80 | 81 | # this whole stuff is just to not execute certain parts 82 | # (speed improvement), basically we could just call 83 | # ``eval_call_path`` on the call_path and it would 84 | # also work. 85 | def listRightIndex(lst, value): 86 | return len(lst) - lst[-1::-1].index(value) - 1 87 | 88 | # Need to take right index, because there could be a 89 | # func usage before. 90 | call_path_simple = [unicode(d) if isinstance(d, pr.NamePart) 91 | else d for d in call_path] 92 | i = listRightIndex(call_path_simple, func_name) 93 | first, last = call_path[:i], call_path[i + 1:] 94 | if not last and not call_path_simple.index(func_name) != i: 95 | continue 96 | scopes = [scope] 97 | if first: 98 | scopes = evaluator.eval_call_path(iter(first), c.parent, pos) 99 | pos = None 100 | from jedi.evaluate import representation as er 101 | for scope in scopes: 102 | s = evaluator.find_types(scope, func_name, position=pos, 103 | search_global=not first, 104 | resolve_decorator=False) 105 | 106 | c = [getattr(escope, 'base_func', None) or escope.base 107 | for escope in s 108 | if escope.isinstance(er.Function, er.Class)] 109 | if compare in c: 110 | # only if we have the correct function we execute 111 | # it, otherwise just ignore it. 112 | evaluator.follow_path(iter(last), s, scope) 113 | return listener.param_possibilities 114 | 115 | result = [] 116 | for params in get_posibilities(evaluator, module, func_name): 117 | for p in params: 118 | if str(p) == param_name: 119 | result += evaluator.eval_statement(p.parent) 120 | return result 121 | 122 | func = param.get_parent_until(pr.Function) 123 | current_module = param.get_parent_until() 124 | func_name = unicode(func.name) 125 | compare = func 126 | if func_name == '__init__' and isinstance(func.parent, pr.Class): 127 | func_name = unicode(func.parent.name) 128 | compare = func.parent 129 | 130 | # get the param name 131 | if param.assignment_details: 132 | # first assignment details, others would be a syntax error 133 | expression_list, op = param.assignment_details[0] 134 | else: 135 | expression_list = param.expression_list() 136 | offset = 1 if expression_list[0] in ['*', '**'] else 0 137 | param_name = str(expression_list[offset].name) 138 | 139 | # add the listener 140 | listener = ParamListener() 141 | func.listeners.add(listener) 142 | 143 | result = [] 144 | # This is like backtracking: Get the first possible result. 145 | for mod in imports.get_modules_containing_name([current_module], func_name): 146 | result = get_params_for_module(mod) 147 | if result: 148 | break 149 | 150 | # cleanup: remove the listener; important: should not stick. 151 | func.listeners.remove(listener) 152 | 153 | return result 154 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/recursion.py: -------------------------------------------------------------------------------- 1 | """ 2 | Recursions are the recipe of |jedi| to conquer Python code. However, someone 3 | must stop recursions going mad. Some settings are here to make |jedi| stop at 4 | the right time. You can read more about them :ref:`here `. 5 | 6 | Next to :mod:`jedi.evaluate.cache` this module also makes |jedi| not 7 | thread-safe. Why? ``ExecutionRecursionDecorator`` uses class variables to 8 | count the function calls. 9 | """ 10 | from jedi.parser import representation as pr 11 | from jedi import debug 12 | from jedi import settings 13 | from jedi.evaluate import compiled 14 | from jedi.evaluate import iterable 15 | 16 | 17 | def recursion_decorator(func): 18 | def run(evaluator, stmt, *args, **kwargs): 19 | rec_detect = evaluator.recursion_detector 20 | # print stmt, len(self.node_statements()) 21 | if rec_detect.push_stmt(stmt): 22 | return [] 23 | else: 24 | result = func(evaluator, stmt, *args, **kwargs) 25 | rec_detect.pop_stmt() 26 | return result 27 | return run 28 | 29 | 30 | class RecursionDetector(object): 31 | """ 32 | A decorator to detect recursions in statements. In a recursion a statement 33 | at the same place, in the same module may not be executed two times. 34 | """ 35 | def __init__(self): 36 | self.top = None 37 | self.current = None 38 | 39 | def push_stmt(self, stmt): 40 | self.current = _RecursionNode(stmt, self.current) 41 | check = self._check_recursion() 42 | if check: 43 | debug.warning('catched stmt recursion: %s against %s @%s', stmt, 44 | check.stmt, stmt.start_pos) 45 | self.pop_stmt() 46 | return True 47 | return False 48 | 49 | def pop_stmt(self): 50 | if self.current is not None: 51 | # I don't know how current can be None, but sometimes it happens 52 | # with Python3. 53 | self.current = self.current.parent 54 | 55 | def _check_recursion(self): 56 | test = self.current 57 | while True: 58 | test = test.parent 59 | if self.current == test: 60 | return test 61 | if not test: 62 | return False 63 | 64 | def node_statements(self): 65 | result = [] 66 | n = self.current 67 | while n: 68 | result.insert(0, n.stmt) 69 | n = n.parent 70 | return result 71 | 72 | 73 | class _RecursionNode(object): 74 | """ A node of the RecursionDecorator. """ 75 | def __init__(self, stmt, parent): 76 | self.script = stmt.get_parent_until() 77 | self.position = stmt.start_pos 78 | self.parent = parent 79 | self.stmt = stmt 80 | 81 | # Don't check param instances, they are not causing recursions 82 | # The same's true for the builtins, because the builtins are really 83 | # simple. 84 | self.is_ignored = isinstance(stmt, pr.Param) \ 85 | or (self.script == compiled.builtin) 86 | 87 | def __eq__(self, other): 88 | if not other: 89 | return None 90 | 91 | # List Comprehensions start on the same line as its statement. 92 | # Therefore we have the unfortunate situation of the same start_pos for 93 | # two statements. 94 | is_list_comp = lambda x: isinstance(x, pr.ListComprehension) 95 | return self.script == other.script \ 96 | and self.position == other.position \ 97 | and not is_list_comp(self.stmt.parent) \ 98 | and not is_list_comp(other.parent) \ 99 | and not self.is_ignored and not other.is_ignored 100 | 101 | 102 | def execution_recursion_decorator(func): 103 | def run(execution, evaluate_generator=False): 104 | detector = execution._evaluator.execution_recursion_detector 105 | if detector.push_execution(execution, evaluate_generator): 106 | result = [] 107 | else: 108 | result = func(execution, evaluate_generator) 109 | detector.pop_execution() 110 | return result 111 | 112 | return run 113 | 114 | 115 | class ExecutionRecursionDetector(object): 116 | """ 117 | Catches recursions of executions. 118 | It is designed like a Singelton. Only one instance should exist. 119 | """ 120 | def __init__(self): 121 | self.recursion_level = 0 122 | self.parent_execution_funcs = [] 123 | self.execution_funcs = set() 124 | self.execution_count = 0 125 | 126 | def __call__(self, execution, evaluate_generator=False): 127 | debug.dbg('Execution recursions: %s', execution, self.recursion_level, 128 | self.execution_count, len(self.execution_funcs)) 129 | if self.check_recursion(execution, evaluate_generator): 130 | result = [] 131 | else: 132 | result = self.func(execution, evaluate_generator) 133 | self.pop_execution() 134 | return result 135 | 136 | def pop_execution(cls): 137 | cls.parent_execution_funcs.pop() 138 | cls.recursion_level -= 1 139 | 140 | def push_execution(cls, execution, evaluate_generator): 141 | in_par_execution_funcs = execution.base in cls.parent_execution_funcs 142 | in_execution_funcs = execution.base in cls.execution_funcs 143 | cls.recursion_level += 1 144 | cls.execution_count += 1 145 | cls.execution_funcs.add(execution.base) 146 | cls.parent_execution_funcs.append(execution.base) 147 | 148 | if cls.execution_count > settings.max_executions: 149 | return True 150 | 151 | if isinstance(execution.base, (iterable.Array, iterable.Generator)): 152 | return False 153 | module = execution.get_parent_until() 154 | if evaluate_generator or module == compiled.builtin: 155 | return False 156 | 157 | if in_par_execution_funcs: 158 | if cls.recursion_level > settings.max_function_recursion_level: 159 | return True 160 | if in_execution_funcs and \ 161 | len(cls.execution_funcs) > settings.max_until_execution_unique: 162 | return True 163 | if cls.execution_count > settings.max_executions_without_builtins: 164 | return True 165 | return False 166 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/stdlib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementations of standard library functions, because it's not possible to 3 | understand them with Jedi. 4 | """ 5 | from jedi._compatibility import unicode 6 | from jedi.evaluate import compiled 7 | from jedi.evaluate import representation as er 8 | from jedi.evaluate import iterable 9 | from jedi.evaluate.helpers import FakeArray, FakeStatement 10 | from jedi.parser import representation as pr 11 | from jedi import debug 12 | 13 | 14 | class NotInStdLib(LookupError): 15 | pass 16 | 17 | 18 | def execute(evaluator, obj, params): 19 | try: 20 | obj_name = str(obj.name) 21 | except AttributeError: 22 | pass 23 | else: 24 | if obj.parent == compiled.builtin: 25 | module_name = 'builtins' 26 | elif isinstance(obj.parent, pr.Module): 27 | module_name = str(obj.parent.name) 28 | else: 29 | module_name = '' 30 | 31 | # for now we just support builtin functions. 32 | try: 33 | return _implemented[module_name][obj_name](evaluator, obj, params) 34 | except KeyError: 35 | pass 36 | raise NotInStdLib() 37 | 38 | 39 | def _follow_param(evaluator, params, index): 40 | try: 41 | stmt = params[index] 42 | except IndexError: 43 | return [] 44 | else: 45 | if isinstance(stmt, pr.Statement): 46 | return evaluator.eval_statement(stmt) 47 | else: 48 | return [stmt] # just some arbitrary object 49 | 50 | 51 | def builtins_getattr(evaluator, obj, params): 52 | stmts = [] 53 | # follow the first param 54 | objects = _follow_param(evaluator, params, 0) 55 | names = _follow_param(evaluator, params, 1) 56 | for obj in objects: 57 | if not isinstance(obj, (er.Instance, er.Class, pr.Module, compiled.CompiledObject)): 58 | debug.warning('getattr called without instance') 59 | continue 60 | 61 | for name in names: 62 | s = unicode, str 63 | if isinstance(name, compiled.CompiledObject) and isinstance(name.obj, s): 64 | stmts += evaluator.follow_path(iter([name.obj]), [obj], obj) 65 | else: 66 | debug.warning('getattr called without str') 67 | continue 68 | return stmts 69 | 70 | 71 | def builtins_type(evaluator, obj, params): 72 | if len(params) == 1: 73 | # otherwise it would be a metaclass... maybe someday... 74 | objects = _follow_param(evaluator, params, 0) 75 | return [o.base for o in objects if isinstance(o, er.Instance)] 76 | return [] 77 | 78 | 79 | def builtins_super(evaluator, obj, params): 80 | # TODO make this able to detect multiple inheritance super 81 | accept = (pr.Function,) 82 | func = params.get_parent_until(accept) 83 | if func.isinstance(*accept): 84 | cls = func.get_parent_until(accept + (pr.Class,), 85 | include_current=False) 86 | if isinstance(cls, pr.Class): 87 | cls = er.Class(evaluator, cls) 88 | su = cls.get_super_classes() 89 | if su: 90 | return evaluator.execute(su[0]) 91 | return [] 92 | 93 | 94 | def builtins_reversed(evaluator, obj, params): 95 | objects = tuple(_follow_param(evaluator, params, 0)) 96 | if objects: 97 | # unpack the iterator values 98 | objects = tuple(iterable.get_iterator_types(objects)) 99 | if objects: 100 | rev = reversed(objects) 101 | # Repack iterator values and then run it the normal way. This is 102 | # necessary, because `reversed` is a function and autocompletion 103 | # would fail in certain cases like `reversed(x).__iter__` if we 104 | # just returned the result directly. 105 | stmts = [FakeStatement([r]) for r in rev] 106 | objects = (iterable.Array(evaluator, FakeArray(stmts, objects[0].parent)),) 107 | return [er.Instance(evaluator, obj, objects)] 108 | 109 | 110 | def _return_first_param(evaluator, obj, params): 111 | if len(params) == 1: 112 | return _follow_param(evaluator, params, 0) 113 | return [] 114 | 115 | 116 | _implemented = { 117 | 'builtins': { 118 | 'getattr': builtins_getattr, 119 | 'type': builtins_type, 120 | 'super': builtins_super, 121 | 'reversed': builtins_reversed, 122 | }, 123 | 'copy': { 124 | 'copy': _return_first_param, 125 | 'deepcopy': _return_first_param, 126 | }, 127 | 'json': { 128 | 'load': lambda *args: [], 129 | 'loads': lambda *args: [], 130 | }, 131 | } 132 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/evaluate/sys_path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from jedi._compatibility import exec_function, unicode 5 | from jedi.parser import representation as pr 6 | from jedi.parser import Parser 7 | from jedi.evaluate.cache import memoize_default 8 | from jedi import debug 9 | from jedi import common 10 | 11 | 12 | def get_sys_path(): 13 | def check_virtual_env(sys_path): 14 | """ Add virtualenv's site-packages to the `sys.path`.""" 15 | venv = os.getenv('VIRTUAL_ENV') 16 | if not venv: 17 | return 18 | venv = os.path.abspath(venv) 19 | if os.name == 'nt': 20 | p = os.path.join(venv, 'lib', 'site-packages') 21 | else: 22 | p = os.path.join(venv, 'lib', 'python%d.%d' % sys.version_info[:2], 23 | 'site-packages') 24 | if p not in sys_path: 25 | sys_path.insert(0, p) 26 | 27 | check_virtual_env(sys.path) 28 | return [p for p in sys.path if p != ""] 29 | 30 | 31 | def _execute_code(module_path, code): 32 | c = "import os; from os.path import *; result=%s" 33 | variables = {'__file__': module_path} 34 | try: 35 | exec_function(c % code, variables) 36 | except Exception: 37 | debug.warning('sys.path manipulation detected, but failed to evaluate.') 38 | return None 39 | try: 40 | res = variables['result'] 41 | if isinstance(res, str): 42 | return os.path.abspath(res) 43 | else: 44 | return None 45 | except KeyError: 46 | return None 47 | 48 | 49 | def _paths_from_assignment(statement): 50 | """ 51 | extracts the assigned strings from an assignment that looks as follows:: 52 | 53 | >>> sys.path[0:0] = ['module/path', 'another/module/path'] 54 | """ 55 | 56 | names = statement.get_defined_names() 57 | if len(names) != 1: 58 | return [] 59 | if [unicode(x) for x in names[0].names] != ['sys', 'path']: 60 | return [] 61 | expressions = statement.expression_list() 62 | if len(expressions) != 1 or not isinstance(expressions[0], pr.Array): 63 | return 64 | stmts = (s for s in expressions[0].values if isinstance(s, pr.Statement)) 65 | expression_lists = (s.expression_list() for s in stmts) 66 | return [e.value for exprs in expression_lists for e in exprs 67 | if isinstance(e, pr.Literal) and e.value] 68 | 69 | 70 | def _paths_from_insert(module_path, exe): 71 | """ extract the inserted module path from an "sys.path.insert" statement 72 | """ 73 | exe_type, exe.type = exe.type, pr.Array.NOARRAY 74 | exe_pop = exe.values.pop(0) 75 | res = _execute_code(module_path, exe.get_code()) 76 | exe.type = exe_type 77 | exe.values.insert(0, exe_pop) 78 | return res 79 | 80 | 81 | def _paths_from_call_expression(module_path, call): 82 | """ extract the path from either "sys.path.append" or "sys.path.insert" """ 83 | if call.execution is None: 84 | return 85 | n = call.name 86 | if not isinstance(n, pr.Name) or len(n.names) != 3: 87 | return 88 | names = [unicode(x) for x in n.names] 89 | if names[:2] != ['sys', 'path']: 90 | return 91 | cmd = names[2] 92 | exe = call.execution 93 | if cmd == 'insert' and len(exe) == 2: 94 | path = _paths_from_insert(module_path, exe) 95 | elif cmd == 'append' and len(exe) == 1: 96 | path = _execute_code(module_path, exe.get_code()) 97 | return path and [path] or [] 98 | 99 | 100 | def _check_module(module): 101 | try: 102 | possible_stmts = module.used_names['path'] 103 | except KeyError: 104 | return get_sys_path() 105 | sys_path = list(get_sys_path()) # copy 106 | statements = (p for p in possible_stmts if isinstance(p, pr.Statement)) 107 | for stmt in statements: 108 | expressions = stmt.expression_list() 109 | if len(expressions) == 1 and isinstance(expressions[0], pr.Call): 110 | sys_path.extend( 111 | _paths_from_call_expression(module.path, expressions[0]) or []) 112 | elif ( 113 | hasattr(stmt, 'assignment_details') and 114 | len(stmt.assignment_details) == 1 115 | ): 116 | sys_path.extend(_paths_from_assignment(stmt) or []) 117 | return sys_path 118 | 119 | 120 | @memoize_default(evaluator_is_first_arg=True) 121 | def sys_path_with_modifications(evaluator, module): 122 | if module.path is None: 123 | # Support for modules without a path is bad, therefore return the 124 | # normal path. 125 | return list(get_sys_path()) 126 | 127 | curdir = os.path.abspath(os.curdir) 128 | with common.ignored(OSError): 129 | os.chdir(os.path.dirname(module.path)) 130 | 131 | result = _check_module(module) 132 | result += _detect_django_path(module.path) 133 | # buildout scripts often contain the same sys.path modifications 134 | # the set here is used to avoid duplicate sys.path entries 135 | buildout_paths = set() 136 | for module_path in _get_buildout_scripts(module.path): 137 | try: 138 | with open(module_path, 'rb') as f: 139 | source = f.read() 140 | except IOError: 141 | pass 142 | else: 143 | p = Parser(common.source_to_unicode(source), module_path) 144 | for path in _check_module(p.module): 145 | if path not in buildout_paths: 146 | buildout_paths.add(path) 147 | result.append(path) 148 | # cleanup, back to old directory 149 | os.chdir(curdir) 150 | return list(result) 151 | 152 | 153 | def _traverse_parents(path): 154 | while True: 155 | new = os.path.dirname(path) 156 | if new == path: 157 | return 158 | path = new 159 | yield path 160 | 161 | 162 | def _get_parent_dir_with_file(path, filename): 163 | for parent in _traverse_parents(path): 164 | if os.path.isfile(os.path.join(parent, filename)): 165 | return parent 166 | return None 167 | 168 | 169 | def _detect_django_path(module_path): 170 | """ Detects the path of the very well known Django library (if used) """ 171 | result = [] 172 | 173 | for parent in _traverse_parents(module_path): 174 | with common.ignored(IOError): 175 | with open(parent + os.path.sep + 'manage.py'): 176 | debug.dbg('Found django path: %s', module_path) 177 | result.append(parent) 178 | return result 179 | 180 | 181 | def _get_buildout_scripts(module_path): 182 | """ 183 | if there is a 'buildout.cfg' file in one of the parent directories of the 184 | given module it will return a list of all files in the buildout bin 185 | directory that look like python files. 186 | 187 | :param module_path: absolute path to the module. 188 | :type module_path: str 189 | """ 190 | project_root = _get_parent_dir_with_file(module_path, 'buildout.cfg') 191 | if not project_root: 192 | return [] 193 | bin_path = os.path.join(project_root, 'bin') 194 | if not os.path.exists(bin_path): 195 | return [] 196 | extra_module_paths = [] 197 | for filename in os.listdir(bin_path): 198 | try: 199 | filepath = os.path.join(bin_path, filename) 200 | with open(filepath, 'r') as f: 201 | firstline = f.readline() 202 | if firstline.startswith('#!') and 'python' in firstline: 203 | extra_module_paths.append(filepath) 204 | except IOError as e: 205 | # either permission error or race cond. because file got deleted 206 | # ignore 207 | debug.warning(unicode(e)) 208 | continue 209 | return extra_module_paths 210 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/refactoring.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduce some basic refactoring functions to |jedi|. This module is still in a 3 | very early development stage and needs much testing and improvement. 4 | 5 | .. warning:: I won't do too much here, but if anyone wants to step in, please 6 | do. Refactoring is none of my priorities 7 | 8 | It uses the |jedi| `API `_ and supports currently the 9 | following functions (sometimes bug-prone): 10 | 11 | - rename 12 | - extract variable 13 | - inline variable 14 | """ 15 | import difflib 16 | 17 | from jedi import common 18 | from jedi.evaluate import helpers 19 | from jedi.parser import representation as pr 20 | 21 | 22 | class Refactoring(object): 23 | def __init__(self, change_dct): 24 | """ 25 | :param change_dct: dict(old_path=(new_path, old_lines, new_lines)) 26 | """ 27 | self.change_dct = change_dct 28 | 29 | def old_files(self): 30 | dct = {} 31 | for old_path, (new_path, old_l, new_l) in self.change_dct.items(): 32 | dct[new_path] = '\n'.join(new_l) 33 | return dct 34 | 35 | def new_files(self): 36 | dct = {} 37 | for old_path, (new_path, old_l, new_l) in self.change_dct.items(): 38 | dct[new_path] = '\n'.join(new_l) 39 | return dct 40 | 41 | def diff(self): 42 | texts = [] 43 | for old_path, (new_path, old_l, new_l) in self.change_dct.items(): 44 | if old_path: 45 | udiff = difflib.unified_diff(old_l, new_l) 46 | else: 47 | udiff = difflib.unified_diff(old_l, new_l, old_path, new_path) 48 | texts.append('\n'.join(udiff)) 49 | return '\n'.join(texts) 50 | 51 | 52 | def rename(script, new_name): 53 | """ The `args` / `kwargs` params are the same as in `api.Script`. 54 | :param operation: The refactoring operation to execute. 55 | :type operation: str 56 | :type source: str 57 | :return: list of changed lines/changed files 58 | """ 59 | return Refactoring(_rename(script.usages(), new_name)) 60 | 61 | 62 | def _rename(names, replace_str): 63 | """ For both rename and inline. """ 64 | order = sorted(names, key=lambda x: (x.module_path, x.line, x.column), 65 | reverse=True) 66 | 67 | def process(path, old_lines, new_lines): 68 | if new_lines is not None: # goto next file, save last 69 | dct[path] = path, old_lines, new_lines 70 | 71 | dct = {} 72 | current_path = object() 73 | new_lines = old_lines = None 74 | for name in order: 75 | if name.in_builtin_module(): 76 | continue 77 | if current_path != name.module_path: 78 | current_path = name.module_path 79 | 80 | process(current_path, old_lines, new_lines) 81 | if current_path is not None: 82 | # None means take the source that is a normal param. 83 | with open(current_path) as f: 84 | source = f.read() 85 | 86 | new_lines = common.splitlines(common.source_to_unicode(source)) 87 | old_lines = new_lines[:] 88 | 89 | nr, indent = name.line, name.column 90 | line = new_lines[nr - 1] 91 | new_lines[nr - 1] = line[:indent] + replace_str + \ 92 | line[indent + len(name.name):] 93 | process(current_path, old_lines, new_lines) 94 | return dct 95 | 96 | 97 | def extract(script, new_name): 98 | """ The `args` / `kwargs` params are the same as in `api.Script`. 99 | :param operation: The refactoring operation to execute. 100 | :type operation: str 101 | :type source: str 102 | :return: list of changed lines/changed files 103 | """ 104 | new_lines = common.splitlines(common.source_to_unicode(script.source)) 105 | old_lines = new_lines[:] 106 | 107 | user_stmt = script._parser.user_stmt() 108 | 109 | # TODO care for multiline extracts 110 | dct = {} 111 | if user_stmt: 112 | pos = script._pos 113 | line_index = pos[0] - 1 114 | arr, index = helpers.array_for_pos(user_stmt, pos) 115 | if arr is not None: 116 | start_pos = arr[index].start_pos 117 | end_pos = arr[index].end_pos 118 | 119 | # take full line if the start line is different from end line 120 | e = end_pos[1] if end_pos[0] == start_pos[0] else None 121 | start_line = new_lines[start_pos[0] - 1] 122 | text = start_line[start_pos[1]:e] 123 | for l in range(start_pos[0], end_pos[0] - 1): 124 | text += '\n' + l 125 | if e is None: 126 | end_line = new_lines[end_pos[0] - 1] 127 | text += '\n' + end_line[:end_pos[1]] 128 | 129 | # remove code from new lines 130 | t = text.lstrip() 131 | del_start = start_pos[1] + len(text) - len(t) 132 | 133 | text = t.rstrip() 134 | del_end = len(t) - len(text) 135 | if e is None: 136 | new_lines[end_pos[0] - 1] = end_line[end_pos[1] - del_end:] 137 | e = len(start_line) 138 | else: 139 | e = e - del_end 140 | start_line = start_line[:del_start] + new_name + start_line[e:] 141 | new_lines[start_pos[0] - 1] = start_line 142 | new_lines[start_pos[0]:end_pos[0] - 1] = [] 143 | 144 | # add parentheses in multiline case 145 | open_brackets = ['(', '[', '{'] 146 | close_brackets = [')', ']', '}'] 147 | if '\n' in text and not (text[0] in open_brackets and text[-1] == 148 | close_brackets[open_brackets.index(text[0])]): 149 | text = '(%s)' % text 150 | 151 | # add new line before statement 152 | indent = user_stmt.start_pos[1] 153 | new = "%s%s = %s" % (' ' * indent, new_name, text) 154 | new_lines.insert(line_index, new) 155 | dct[script.path] = script.path, old_lines, new_lines 156 | return Refactoring(dct) 157 | 158 | 159 | def inline(script): 160 | """ 161 | :type script: api.Script 162 | """ 163 | new_lines = common.splitlines(common.source_to_unicode(script.source)) 164 | 165 | dct = {} 166 | 167 | definitions = script.goto_assignments() 168 | with common.ignored(AssertionError): 169 | assert len(definitions) == 1 170 | stmt = definitions[0]._definition 171 | usages = script.usages() 172 | inlines = [r for r in usages 173 | if not stmt.start_pos <= (r.line, r.column) <= stmt.end_pos] 174 | inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column), 175 | reverse=True) 176 | expression_list = stmt.expression_list() 177 | # don't allow multiline refactorings for now. 178 | assert stmt.start_pos[0] == stmt.end_pos[0] 179 | index = stmt.start_pos[0] - 1 180 | 181 | line = new_lines[index] 182 | replace_str = line[expression_list[0].start_pos[1]:stmt.end_pos[1] + 1] 183 | replace_str = replace_str.strip() 184 | # tuples need parentheses 185 | if expression_list and isinstance(expression_list[0], pr.Array): 186 | arr = expression_list[0] 187 | if replace_str[0] not in ['(', '[', '{'] and len(arr) > 1: 188 | replace_str = '(%s)' % replace_str 189 | 190 | # if it's the only assignment, remove the statement 191 | if len(stmt.get_defined_names()) == 1: 192 | line = line[:stmt.start_pos[1]] + line[stmt.end_pos[1]:] 193 | 194 | dct = _rename(inlines, replace_str) 195 | # remove the empty line 196 | new_lines = dct[script.path][2] 197 | if line.strip(): 198 | new_lines[index] = line 199 | else: 200 | new_lines.pop(index) 201 | 202 | return Refactoring(dct) 203 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains variables with global |jedi| settings. To change the 3 | behavior of |jedi|, change the variables defined in :mod:`jedi.settings`. 4 | 5 | Plugins should expose an interface so that the user can adjust the 6 | configuration. 7 | 8 | 9 | Example usage:: 10 | 11 | from jedi import settings 12 | settings.case_insensitive_completion = True 13 | 14 | 15 | Completion output 16 | ~~~~~~~~~~~~~~~~~ 17 | 18 | .. autodata:: case_insensitive_completion 19 | .. autodata:: add_dot_after_module 20 | .. autodata:: add_bracket_after_function 21 | .. autodata:: no_completion_duplicates 22 | 23 | 24 | Filesystem cache 25 | ~~~~~~~~~~~~~~~~ 26 | 27 | .. autodata:: cache_directory 28 | .. autodata:: use_filesystem_cache 29 | 30 | 31 | Parser 32 | ~~~~~~ 33 | 34 | .. autodata:: fast_parser 35 | 36 | 37 | Dynamic stuff 38 | ~~~~~~~~~~~~~ 39 | 40 | .. autodata:: dynamic_arrays_instances 41 | .. autodata:: dynamic_array_additions 42 | .. autodata:: dynamic_params 43 | .. autodata:: dynamic_params_for_other_modules 44 | .. autodata:: additional_dynamic_modules 45 | .. autodata:: auto_import_modules 46 | 47 | 48 | .. _settings-recursion: 49 | 50 | Recursions 51 | ~~~~~~~~~~ 52 | 53 | Recursion settings are important if you don't want extremly 54 | recursive python code to go absolutely crazy. First of there is a 55 | global limit :data:`max_executions`. This limit is important, to set 56 | a maximum amount of time, the completion may use. 57 | 58 | The default values are based on experiments while completing the |jedi| library 59 | itself (inception!). But I don't think there's any other Python library that 60 | uses recursion in a similarly extreme way. These settings make the completion 61 | definitely worse in some cases. But a completion should also be fast. 62 | 63 | .. autodata:: max_until_execution_unique 64 | .. autodata:: max_function_recursion_level 65 | .. autodata:: max_executions_without_builtins 66 | .. autodata:: max_executions 67 | .. autodata:: scale_call_signatures 68 | 69 | 70 | Caching 71 | ~~~~~~~ 72 | 73 | .. autodata:: star_import_cache_validity 74 | .. autodata:: call_signatures_validity 75 | 76 | 77 | """ 78 | import os 79 | import platform 80 | 81 | # ---------------- 82 | # completion output settings 83 | # ---------------- 84 | 85 | case_insensitive_completion = True 86 | """ 87 | The completion is by default case insensitive. 88 | """ 89 | 90 | add_dot_after_module = False 91 | """ 92 | Adds a dot after a module, because a module that is not accessed this way is 93 | definitely not the normal case. However, in VIM this doesn't work, that's why 94 | it isn't used at the moment. 95 | """ 96 | 97 | add_bracket_after_function = False 98 | """ 99 | Adds an opening bracket after a function, because that's normal behaviour. 100 | Removed it again, because in VIM that is not very practical. 101 | """ 102 | 103 | no_completion_duplicates = True 104 | """ 105 | If set, completions with the same name don't appear in the output anymore, 106 | but are in the `same_name_completions` attribute. 107 | """ 108 | 109 | # ---------------- 110 | # Filesystem cache 111 | # ---------------- 112 | 113 | use_filesystem_cache = True 114 | """ 115 | Use filesystem cache to save once parsed files with pickle. 116 | """ 117 | 118 | if platform.system().lower() == 'windows': 119 | _cache_directory = os.path.join(os.getenv('APPDATA') or '~', 'Jedi', 120 | 'Jedi') 121 | elif platform.system().lower() == 'darwin': 122 | _cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi') 123 | else: 124 | _cache_directory = os.path.join(os.getenv('XDG_CACHE_HOME') or '~/.cache', 125 | 'jedi') 126 | cache_directory = os.path.expanduser(_cache_directory) 127 | """ 128 | The path where all the caches can be found. 129 | 130 | On Linux, this defaults to ``~/.cache/jedi/``, on OS X to 131 | ``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``. 132 | On Linux, if environment variable ``$XDG_CACHE_HOME`` is set, 133 | ``$XDG_CACHE_HOME/jedi`` is used instead of the default one. 134 | """ 135 | 136 | # ---------------- 137 | # parser 138 | # ---------------- 139 | 140 | fast_parser = True 141 | """ 142 | Use the fast parser. This means that reparsing is only being done if 143 | something has been changed e.g. to a function. If this happens, only the 144 | function is being reparsed. 145 | """ 146 | 147 | # ---------------- 148 | # dynamic stuff 149 | # ---------------- 150 | 151 | dynamic_arrays_instances = True 152 | """ 153 | Check for `append`, etc. on array instances like list() 154 | """ 155 | 156 | dynamic_array_additions = True 157 | """ 158 | check for `append`, etc. on arrays: [], {}, () 159 | """ 160 | 161 | dynamic_params = True 162 | """ 163 | A dynamic param completion, finds the callees of the function, which define 164 | the params of a function. 165 | """ 166 | 167 | dynamic_params_for_other_modules = True 168 | """ 169 | Do the same for other modules. 170 | """ 171 | 172 | additional_dynamic_modules = [] 173 | """ 174 | Additional modules in which |jedi| checks if statements are to be found. This 175 | is practical for IDEs, that want to administrate their modules themselves. 176 | """ 177 | 178 | dynamic_flow_information = True 179 | """ 180 | Check for `isinstance` and other information to infer a type. 181 | """ 182 | 183 | auto_import_modules = [ 184 | 'hashlib', # setattr 185 | ] 186 | """ 187 | Modules that are not analyzed but imported, although they contain Python code. 188 | This improves autocompletion for libraries that use ``setattr`` or 189 | ``globals()`` modifications a lot. 190 | """ 191 | 192 | # ---------------- 193 | # recursions 194 | # ---------------- 195 | 196 | max_until_execution_unique = 50 197 | """ 198 | This limit is probably the most important one, because if this limit is 199 | exceeded, functions can only be one time executed. So new functions will be 200 | executed, complex recursions with the same functions again and again, are 201 | ignored. 202 | """ 203 | 204 | max_function_recursion_level = 5 205 | """ 206 | `max_function_recursion_level` is more about whether the recursions are 207 | stopped in deepth or in width. The ratio beetween this and 208 | `max_until_execution_unique` is important here. It stops a recursion (after 209 | the number of function calls in the recursion), if it was already used 210 | earlier. 211 | """ 212 | 213 | max_executions_without_builtins = 200 214 | """ 215 | .. todo:: Document this. 216 | """ 217 | 218 | max_executions = 250 219 | """ 220 | A maximum amount of time, the completion may use. 221 | """ 222 | 223 | scale_call_signatures = 0.1 224 | """ 225 | Because call_signatures is normally used on every single key hit, it has 226 | to be faster than a normal completion. This is the factor that is used to 227 | scale `max_executions` and `max_until_execution_unique`: 228 | """ 229 | 230 | # ---------------- 231 | # caching validity (time) 232 | # ---------------- 233 | 234 | star_import_cache_validity = 60.0 235 | """ 236 | In huge packages like numpy, checking all star imports on every completion 237 | might be slow, therefore we do a star import caching, that lasts a certain 238 | time span (in seconds). 239 | """ 240 | 241 | call_signatures_validity = 3.0 242 | """ 243 | Finding function calls might be slow (0.1-0.5s). This is not acceptible for 244 | normal writing. Therefore cache it for a short time. 245 | """ 246 | -------------------------------------------------------------------------------- /multi_script_editor/jedi/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for end-users. 3 | """ 4 | 5 | from __future__ import absolute_import 6 | import __main__ 7 | from collections import namedtuple 8 | import re 9 | import os 10 | import sys 11 | 12 | from jedi import Interpreter 13 | from jedi.api.helpers import completion_parts 14 | from jedi.parser.user_context import UserContext 15 | 16 | 17 | def setup_readline(namespace_module=__main__): 18 | """ 19 | Install Jedi completer to :mod:`readline`. 20 | 21 | This function setups :mod:`readline` to use Jedi in Python interactive 22 | shell. If you want to use a custom ``PYTHONSTARTUP`` file (typically 23 | ``$HOME/.pythonrc.py``), you can add this piece of code:: 24 | 25 | try: 26 | from jedi.utils import setup_readline 27 | setup_readline() 28 | except ImportError: 29 | # Fallback to the stdlib readline completer if it is installed. 30 | # Taken from http://docs.python.org/2/library/rlcompleter.html 31 | print("Jedi is not installed, falling back to readline") 32 | try: 33 | import readline 34 | import rlcompleter 35 | readline.parse_and_bind("tab: complete") 36 | except ImportError: 37 | print("Readline is not installed either. No tab completion is enabled.") 38 | 39 | This will fallback to the readline completer if Jedi is not installed. 40 | The readline completer will only complete names in the global namespace, 41 | so for example:: 42 | 43 | ran 44 | 45 | will complete to ``range`` 46 | 47 | with both Jedi and readline, but:: 48 | 49 | range(10).cou 50 | 51 | will show complete to ``range(10).count`` only with Jedi. 52 | 53 | You'll also need to add ``export PYTHONSTARTUP=$HOME/.pythonrc.py`` to 54 | your shell profile (usually ``.bash_profile`` or ``.profile`` if you use 55 | bash). 56 | 57 | """ 58 | class JediRL(object): 59 | def complete(self, text, state): 60 | """ 61 | This complete stuff is pretty weird, a generator would make 62 | a lot more sense, but probably due to backwards compatibility 63 | this is still the way how it works. 64 | 65 | The only important part is stuff in the ``state == 0`` flow, 66 | everything else has been copied from the ``rlcompleter`` std. 67 | library module. 68 | """ 69 | if state == 0: 70 | sys.path.insert(0, os.getcwd()) 71 | # Calling python doesn't have a path, so add to sys.path. 72 | try: 73 | interpreter = Interpreter(text, [namespace_module.__dict__]) 74 | 75 | path = UserContext(text, (1, len(text))).get_path_until_cursor() 76 | path, dot, like = completion_parts(path) 77 | before = text[:len(text) - len(like)] 78 | completions = interpreter.completions() 79 | finally: 80 | sys.path.pop(0) 81 | 82 | self.matches = [before + c.name_with_symbols for c in completions] 83 | try: 84 | return self.matches[state] 85 | except IndexError: 86 | return None 87 | 88 | try: 89 | import readline 90 | except ImportError: 91 | print("Module readline not available.") 92 | else: 93 | readline.set_completer(JediRL().complete) 94 | readline.parse_and_bind("tab: complete") 95 | # jedi itself does the case matching 96 | readline.parse_and_bind("set completion-ignore-case on") 97 | # because it's easier to hit the tab just once 98 | readline.parse_and_bind("set show-all-if-unmodified") 99 | readline.parse_and_bind("set show-all-if-ambiguous on") 100 | # don't repeat all the things written in the readline all the time 101 | readline.parse_and_bind("set completion-prefix-display-length 2") 102 | # No delimiters, Jedi handles that. 103 | readline.set_completer_delims('') 104 | 105 | 106 | def version_info(): 107 | """ 108 | Returns a namedtuple of Jedi's version, similar to Python's 109 | ``sys.version_info``. 110 | """ 111 | Version = namedtuple('Version', 'major, minor, micro, releaselevel, serial') 112 | from jedi import __version__ 113 | tupl = re.findall('[a-z]+|\d+', __version__) 114 | return Version(*[x if i == 3 else int(x) for i, x in enumerate(tupl)]) 115 | -------------------------------------------------------------------------------- /multi_script_editor/managers/_3dsmax.py: -------------------------------------------------------------------------------- 1 | import os, sys, re 2 | try: 3 | from PySide.QtGui import * 4 | from PySide.QtCore import * 5 | except: 6 | from PySide2.QtGui import * 7 | from PySide2.QtCore import * 8 | from PySide2.QtWidgets import * 9 | 10 | from multi_script_editor import scriptEditor 11 | reload(scriptEditor) 12 | import MaxPlus 13 | q3dsmax = QApplication.instance() 14 | 15 | class MaxDialogEvents(QObject): 16 | def eventFilter(self, obj, event): 17 | import MaxPlus 18 | if event.type() == QEvent.WindowActivate: 19 | MaxPlus.CUI.DisableAccelerators() 20 | elif event.type() == QEvent.WindowDeactivate: 21 | MaxPlus.CUI.EnableAccelerators() 22 | return False 23 | 24 | def show(): 25 | try: 26 | qtwindow = MaxPlus.GetQMaxWindow() 27 | except: 28 | qtwindow = MaxPlus.GetQMaxMainWindow() 29 | se = scriptEditor.scriptEditorClass(parent=qtwindow) 30 | #se.installEventFilter(MaxDialogEvents()) 31 | se.runCommand('import MaxPlus') 32 | #se.MaxEventFilter = MaxDialogEvents() 33 | #se.installEventFilter(se.MaxEventFilter) 34 | se.show() 35 | -------------------------------------------------------------------------------- /multi_script_editor/managers/__init__.py: -------------------------------------------------------------------------------- 1 | main = __import__('__main__') 2 | import platform 3 | 4 | 5 | ####### COMPLETERS ############################################## 6 | 7 | # NUKE 8 | def nukeCompleter(*args): 9 | from managers import _nuke 10 | return _nuke.completer(*args) 11 | 12 | def getNukeContextMenu(*args): 13 | from managers import _nuke 14 | reload(_nuke) 15 | return _nuke.contextMenu(*args) 16 | ################################################################### 17 | 18 | # HOUDINI 19 | def houdiniCompleter(*args): 20 | from managers import _houdini 21 | return _houdini.completer(*args) 22 | def getHoudiniContextMenu(*args): 23 | from managers import _houdini 24 | reload(_houdini) 25 | return _houdini.contextMenu(*args) 26 | def houdiniDropEvent(*args): 27 | from managers import _houdini 28 | reload(_houdini) 29 | return _houdini.wrapDroppedText(*args) 30 | ################################################################### 31 | 32 | # MAYA 33 | def mayaCompleter(*args): 34 | from managers import _maya 35 | reload(_maya) 36 | return _maya.completer(*args) 37 | 38 | def mayaDropEvent(*args): 39 | from managers import _maya 40 | return _maya.wrapDroppedText(*args) 41 | def getMayaContextMenu(*args): 42 | from managers import _maya 43 | reload(_maya) 44 | return _maya.contextMenu(*args) 45 | ################################################################### 46 | 47 | 48 | contextCompleters = dict( 49 | nuke=nukeCompleter, 50 | hou=houdiniCompleter, 51 | maya=mayaCompleter 52 | ) 53 | 54 | contextMenus = dict( 55 | hou=getHoudiniContextMenu, 56 | nuke=getNukeContextMenu, 57 | maya=getMayaContextMenu 58 | ) 59 | 60 | dropEvents = dict( 61 | maya=mayaDropEvent, 62 | hou=houdiniDropEvent 63 | ) 64 | 65 | autoImport = dict( 66 | hou='import hou\n', 67 | nuke='import nuke\n', 68 | max='import MaxPlus\n' 69 | ) 70 | mayaDragTempData = 'maya_temp_drag_empty_Data' 71 | 72 | main_parent = None 73 | context = None 74 | if 'hou' in main.__dict__: 75 | context = 'hou' 76 | if main.__dict__['hou'].applicationVersion()[0] >= 15: 77 | main_parent = main.__dict__['hou'].ui.mainQtWindow() 78 | elif 'cmds' in main.__dict__: 79 | context = 'maya' 80 | elif 'nuke' in main.__dict__: 81 | context = 'nuke' 82 | elif 'MaxPlus' in main.__dict__: 83 | context = 'max' 84 | 85 | 86 | 87 | 88 | if platform.system().lower() == 'windows': 89 | _s = 'w' 90 | elif platform.system().lower() == 'darwin': 91 | _s = 'x' 92 | else: 93 | _s = 'l' -------------------------------------------------------------------------------- /multi_script_editor/managers/_nuke.py: -------------------------------------------------------------------------------- 1 | import os, sys, re 2 | # import nuke 3 | main = __import__('__main__') 4 | ns = main.__dict__ 5 | exec 'import nuke' in ns 6 | exec 'import nukescripts' in ns 7 | nuke = ns['nuke'] 8 | import nukescripts 9 | from managers.nuke import nodes 10 | nuke_nodes = dir(nodes) 11 | from managers.completeWidget import contextCompleterClass 12 | 13 | try: 14 | from PySide.QtGui import * 15 | from PySide.QtCore import * 16 | except ImportError: 17 | from PySide2.QtGui import * 18 | from PySide2.QtCore import * 19 | from PySide2.QtWidgets import * 20 | 21 | p = os.path.dirname(__file__).replace('\\','/') 22 | if not p in sys.path: 23 | sys.path.insert(0, p) 24 | 25 | from multi_script_editor import scriptEditor 26 | reload(scriptEditor) 27 | 28 | # QT 29 | qApp = QApplication.instance() 30 | 31 | def getMainWindow(): 32 | for widget in qApp.topLevelWidgets(): 33 | if widget.metaObject().className() == 'Foundry::UI::DockMainWindow': 34 | return widget 35 | qNuke = getMainWindow() 36 | 37 | def show(panel=False): 38 | if panel: 39 | import multi_script_editor.scriptEditor 40 | nukescripts.panels.registerWidgetAsPanel("multi_script_editor.scriptEditor.scriptEditorClass", "Multi Script Editor", "pw_multi_script_editor") 41 | else: 42 | showWindow() 43 | 44 | 45 | def showWindow(): 46 | se = scriptEditor.scriptEditorClass(qNuke) 47 | se.runCommand('import nuke') 48 | se.show() 49 | 50 | 51 | # add to menu.py 52 | # Add to menu.py 53 | # menubar = nuke.menu("Nuke") 54 | # toolMenu = menubar.addMenu('&Tools') 55 | # path = 'path/to/MultiScriptEditor_module' 56 | # # example c:/nuke/python/lib 57 | # if not path in sys.path: 58 | # sys.path.append(path) 59 | # 60 | # import multi_script_editor 61 | # # add to menu 62 | # toolMenu.addCommand("Multi Script Editor", "multi_script_editor.showNuke()") 63 | # # create new pane 64 | # multi_script_editor.showNuke(panel=True) 65 | 66 | 67 | ############ COMPLETER 68 | 69 | def completer(line, ns): 70 | # node types 71 | p1 = r"nuke\.createNode\(\w*['\"](\w*)$" 72 | m = re.search(p1, line) 73 | if m: 74 | name = m.group(1) 75 | l = len(name) 76 | if name: 77 | auto = [x for x in nuke_nodes if x.lower().startswith(name.lower())] 78 | else: 79 | auto = nuke_nodes 80 | return [contextCompleterClass(x, x[l:], True) for x in auto], None 81 | 82 | # p2 = r"nuke\.allNodes\(.*(filter=)*['\"](\w*)$" 83 | funcs = ['allNodes', 'selectedNodes'] 84 | for f in funcs: 85 | p2 = r"nuke\."+f+"\(\w*(filter=)*['\"]{1}(\w*)$" 86 | m = re.search(p2, line)# or re.search(p2, line) 87 | if m: 88 | name = m.group(2) 89 | l = len(name) 90 | if name: 91 | auto = [x for x in nuke_nodes if x.lower().startswith(name.lower())] 92 | else: 93 | auto = nuke_nodes 94 | return [contextCompleterClass(x, x[l:], True) for x in auto], None 95 | 96 | # exists nodes 97 | p3 = r"nuke\.toNode\(\w*['\"](\w*)$" 98 | m = re.search(p3, line) 99 | if m: 100 | name = m.group(1) 101 | # nuke.tprint(name) 102 | nodes = [x.name() for x in nuke.allNodes()] + ['preferences', 'root'] #recurseGroups=True 103 | if name: 104 | result = [x for x in nodes if x.lower().startswith(name.lower())] 105 | else: 106 | result = nodes 107 | l = len(name) 108 | return [contextCompleterClass(x, x[l:], True) for x in result], None 109 | # node knobs 110 | p4 = r"(\w+)\[['\"]{1}(\w*)$" 111 | m = re.search(p4, line) 112 | if m: 113 | node = m.group(1) 114 | name = m.group(2) 115 | 116 | if node in ns: 117 | if hasattr(ns[node], 'allKnobs'): 118 | names = [x.name() for x in ns[node].allKnobs()] 119 | if name: 120 | result = [x for x in names if x.lower().startswith(name.lower())] 121 | else: 122 | result = names 123 | l = len(name) 124 | return [contextCompleterClass(x, x[l:], True) for x in result if x], None 125 | # nuke.tprint(ns[node]) 126 | return None, None 127 | 128 | ################ CONTEXT MENU 129 | 130 | def contextMenu(parent): 131 | m = nukeContextMenu(parent) 132 | return m 133 | 134 | class nukeContextMenu(QMenu): 135 | def __init__(self, parent): 136 | super(nukeContextMenu, self).__init__('Nuke') 137 | self.par = parent 138 | self.setTearOffEnabled(1) 139 | self.setWindowTitle('MSE %s Nuke' % self.par.ver) 140 | self.addAction(QAction('Read PyScript Knob', parent, triggered=self.readPyScriptKnob)) 141 | self.addAction(QAction('Save To PyScript Knob', parent, triggered=self.saveToKnob)) 142 | self.addSeparator() 143 | self.addAction(QAction('From Selected', parent, triggered=self.nodeToCode)) 144 | self.addAction(QAction('From Clipboard', parent, triggered=self.nodesFromClipboard)) 145 | 146 | def nodeToCode(self): 147 | nodes = nuke.selectedNodes() 148 | names = [x.name() for x in nodes] 149 | result = '\n'.join(["nuke.toNode('%s')" % x for x in names]) 150 | self.par.insertText(result) 151 | 152 | def getPyKnob(self, title): 153 | s = nuke.selectedNodes() 154 | if s: 155 | s = s[0] 156 | pyKnobs = [x for x in s.knobs().values() if x.Class() in ['PyScript_Knob','PythonCustomKnob']] 157 | if pyKnobs: 158 | result = {} 159 | for k in pyKnobs: 160 | result[k.name()] = k 161 | if result: 162 | curr = self.par.tab.currentTabName() 163 | selected = curr.split('|')[-1].strip() 164 | dial = selectDialog(result.keys(), title, selected) 165 | if dial.exec_(): 166 | name = dial.list.currentItem().text() 167 | knob = result[name] 168 | return knob 169 | else: 170 | nuke.message('Python Knobs not found') 171 | else: 172 | nuke.message('Select one node') 173 | 174 | def readPyScriptKnob(self): 175 | knob = self.getPyKnob('Select Python Knob to Read') 176 | if knob: 177 | text = knob.value() 178 | self.par.tab.addNewTab(knob.node().name()+' | '+knob.name(), text) 179 | 180 | def saveToKnob(self): 181 | knob = self.getPyKnob('Select Python Knob to Save') 182 | if knob: 183 | text = self.par.tab.getCurrentText() 184 | knob.setValue(text) 185 | 186 | def nodesFromClipboard(self): 187 | # nuke.tprint(str(self.par)) 188 | text = QApplication.clipboard().text() 189 | nodes = [] 190 | if text: 191 | for l in text.split('\n'): 192 | res = re.findall(r'name \w+$', l) 193 | if res: 194 | name = res[0].split()[1] 195 | nodes.append(name) 196 | for n in nodes: 197 | self.par.tab.addToCurrent('nuke.toNode("%s")\n' % n) 198 | 199 | class selectDialog(QDialog): 200 | def __init__(self, items, title, sel=None): 201 | super(selectDialog, self).__init__() 202 | self.setWindowTitle(title) 203 | self.setWindowFlags(Qt.Tool) 204 | self.list = QListWidget(self) 205 | self.list.setSelectionMode(QAbstractItemView.SingleSelection) 206 | self.ly = QVBoxLayout(self) 207 | self.setLayout(self.ly) 208 | self.ly.addWidget(self.list) 209 | self.btn = QPushButton('Select') 210 | self.ly.addWidget(self.btn) 211 | self.btn.clicked.connect(self.accept) 212 | selected = None 213 | for i in items: 214 | item = QListWidgetItem(i) 215 | item.setData(32, i) 216 | self.list.addItem(item) 217 | if i == sel: 218 | selected = item 219 | if selected: 220 | self.list.setCurrentIndex(self.list.indexFromItem(selected)) 221 | 222 | 223 | 224 | def closeEvent(self, *args, **kwargs): 225 | self.reject() 226 | super(selectDialog, self).closeEvent( *args, **kwargs) 227 | 228 | -------------------------------------------------------------------------------- /multi_script_editor/managers/completeWidget.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # context completer 4 | class contextCompleterClass(object): 5 | def __init__(self, name, complete, end=None): 6 | self.name = name 7 | self.complete = complete 8 | self.end_char = end -------------------------------------------------------------------------------- /multi_script_editor/managers/houdini/multi_script_editor_16.pypanel: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /multi_script_editor/managers/nuke/__init__.py: -------------------------------------------------------------------------------- 1 | from main import * 2 | from . import math 3 | import geo 4 | import nodes 5 | 6 | AFTER_CONST = 21 7 | AFTER_LINEAR = 22 8 | ALL = 1 9 | ALWAYS_SAVE = 1048576 10 | BEFORE_CONST = 19 11 | BEFORE_LINEAR = 20 12 | BREAK = 18 13 | CATMULL_ROM = 3 14 | CONSTANT = 1 15 | CUBIC = 4 16 | DISABLED = 128 17 | DO_NOT_WRITE = 512 18 | ENDLINE = 8192 19 | EXE_PATH = '' 20 | EXPRESSIONS = 1 21 | FLOAT = 5 22 | FONT = 4 23 | GEO = 16 24 | GUI = False 25 | HIDDEN_INPUTS = 4 26 | HORIZONTAL = 17 27 | IMAGE = 1 28 | INPUTS = 2 29 | INT16 = 3 30 | INT8 = 2 31 | INTERACTIVE = True 32 | INVISIBLE = 1024 33 | KNOB_CHANGED_RECURSIVE = 134217728 34 | LINEAR = 2 35 | LOG = 4 36 | MATCH_CLASS = 0 37 | MATCH_COLOR = 2 38 | MATCH_LABEL = 1 39 | MONITOR = 0 40 | NODIR = 2 41 | NO_ANIMATION = 256 42 | NO_CHECKMARKS = 1 43 | NO_MULTIVIEW = 1073741824 44 | NO_POSTAGESTAMPS = False 45 | NO_UNDO = 524288 46 | NUKE_VERSION_DATE = '' 47 | NUKE_VERSION_MAJOR = 6 48 | NUKE_VERSION_MINOR = 3 49 | NUKE_VERSION_PHASE = '' 50 | NUKE_VERSION_PHASENUMBER = 0 51 | NUKE_VERSION_RELEASE = 1 52 | NUKE_VERSION_STRING = '' 53 | NUM_CPUS = 4 54 | NUM_INTERPOLATIONS = 5 55 | PLUGIN_EXT = 'dll' 56 | PREPEND = 8 57 | PYTHON = 32 58 | REPLACE = 1 59 | SAVE_MENU = 33554432 60 | SCRIPT = 2 61 | SMOOTH = 0 62 | STARTLINE = 4096 63 | TABBEGINCLOSEDGROUP = 2 64 | TABBEGINGROUP = 1 65 | TABENDGROUP = -1 66 | TABKNOB = 0 67 | THREADS = 4 68 | TO_SCRIPT = 1 69 | TO_VALUE = 2 70 | USER_SET_SLOPE = 16 71 | VIEWER = 1 72 | WRITE_ALL = 8 73 | WRITE_NON_DEFAULT_ONLY = 16 74 | WRITE_USER_KNOB_DEFS = 4 75 | __package__ = 'nuke' 76 | afterBackgroundFrameRenders = [] 77 | afterBackgroundRenders = [] 78 | afterFrameRenders = {} 79 | afterRenders = {} 80 | autoSaveDeleteFilters = {} 81 | autoSaveFilters = {} 82 | autoSaveRestoreFilters = {} 83 | autolabels = {} 84 | beforeBackgroundRenders = [] 85 | beforeFrameRenders = {} 86 | beforeRenders = {} 87 | env = {} 88 | filenameFilters = {} 89 | knobChangeds = {} 90 | # nodes = 91 | onCreates = {} 92 | onDestroys = {} 93 | onScriptCloses = {} 94 | onScriptLoads = {} 95 | onScriptSaves = {} 96 | onUserCreates = {} 97 | rawArgs = [] 98 | untitled = 'Untitled' 99 | updateUIs = {} 100 | validateFilenames = {} 101 | __all__=[] 102 | threading = None -------------------------------------------------------------------------------- /multi_script_editor/managers/nuke/geo.py: -------------------------------------------------------------------------------- 1 | class AttrGroup(object): 2 | Group_Attributes = 5 3 | Group_Last = 6 4 | Group_Matrix = 4 5 | Group_None = -1 6 | Group_Object = 3 7 | Group_Points = 2 8 | Group_Primitives = 0 9 | Group_Vertices = 1 10 | 11 | class AttrType(object): 12 | eFloatAttrib = 0 13 | eIntAttrib = 5 14 | eInvalidAttrib = -1 15 | eMatrix3Attrib = 8 16 | eMatrix4Attrib = 9 17 | eNormalAttrib = 4 18 | ePointerAttrib = 7 19 | eStringAttrib = 6 20 | eVector2Attrib = 1 21 | eVector3Attrib = 2 22 | eVector4Attrib = 3 23 | 24 | class AttribContext(object): 25 | attribute=None 26 | #The attribute itself. 27 | channel=None 28 | #The channel number for the underlying attribute. 29 | group=None 30 | #What this attribute applies to (points, vertices, faces, etc.). 31 | name=None 32 | #The name for the attribute. 33 | recursive=None 34 | #Boolean value to indicate whether or not the attribute is applied recursively. 35 | type=None 36 | #The type of the attribute values. 37 | varying=None 38 | #Boolean value to indicate whether or not the attribute varies. 39 | def empty(self): 40 | """Whether this attribute is empty, i.e. 41 | """ 42 | pass 43 | 44 | class Attribute(object): 45 | name=None 46 | #The name for the attribute. 47 | type=None 48 | #The type of the attribute values. 49 | def invalid(self): 50 | return True 51 | def valid(self): 52 | return True 53 | 54 | 55 | class GeoInfo(object): 56 | def attribContext(self, name, group, type): 57 | """ 58 | Gets an attribute context for the named attribute of this object. 59 | list of (x,y,z) tuples""" 60 | return AttribContext() 61 | 62 | def normals(self): 63 | """ 64 | Gets the list of vertex normals for this object. 65 | PointList""" 66 | return ((0,0,0),) 67 | 68 | def points(self): 69 | """ 70 | Gets the point list for this object. 71 | list of point index lists""" 72 | return PointList() 73 | 74 | def primitives(self): 75 | """ 76 | Gets the list of primitives which make up this object. 77 | 4x4 tuple of floats""" 78 | return [1,] 79 | 80 | def transform(self): 81 | """ 82 | Gets the transform matrix from the objects local coordinate system to world coordinates. 83 | """ 84 | return (0,) 85 | 86 | class GeometryList(object): 87 | def __getitem__(self, x, y): 88 | pass 89 | 90 | class PointList(object): 91 | def __getitem__(self, x, y): 92 | pass 93 | 94 | class Primitive(object): 95 | 96 | def averageCenter(self, pointlist): 97 | """ 98 | Get the average x,y,z location of all points in this primitive. 99 | (x, y, z) """ 100 | return (0,0,0) 101 | 102 | def faceAverageCenter(self, faceIndex, pointlist): 103 | """ 104 | Get the average x,y,z location of all points in a particular face. 105 | list of int""" 106 | return (0,0,0) 107 | 108 | def faceVertices(self, faceIndex): 109 | """ 110 | Get the list of point indexes for a particular face. 111 | int """ 112 | return [0,] 113 | 114 | def faces(self): 115 | """ 116 | Get the number of sub-faces this primitive generates. 117 | (x, y, z) """ 118 | return (0,0,0) 119 | 120 | def normal(self): 121 | """ 122 | Get the normal for this primitive. 123 | list of int""" 124 | return (0,0,0) 125 | 126 | def points(self): 127 | """ 128 | Get the list of point indexes for this primitive.""" 129 | return [0,] -------------------------------------------------------------------------------- /multi_script_editor/managers/nuke/math.py: -------------------------------------------------------------------------------- 1 | class Matrix3(object): 2 | def set(self, v1,v2,v3,v4,v5,v6,v7,v8,v9): 3 | pass 4 | def makeIdentity(self): 5 | pass 6 | def rotateX(self, value): 7 | pass 8 | def rotateY(self, value): 9 | pass 10 | def rotateZ(self, value): 11 | pass 12 | def scale(self, x, y, z): 13 | return 0 14 | def scaling(self, x, y, z): 15 | return 0 16 | def transform(self, vector): 17 | pass 18 | def skew(self, value): 19 | pass 20 | def determinant(self): 21 | pass 22 | def identity(self): 23 | pass 24 | def inverse(self): 25 | return Matrix3() 26 | def rotate(self, vector_or_x, y=0, z=0, w=0): 27 | pass 28 | def rotation(self): 29 | pass 30 | def rotationX(self, value): 31 | pass 32 | def rotationY(self, value): 33 | pass 34 | def rotationZ(self, value): 35 | pass 36 | 37 | 38 | class Matrix4(Matrix3): 39 | def set(self, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16): 40 | pass 41 | def scaleOnly(self): 42 | pass 43 | def rotationOnly(self): 44 | pass 45 | def rotationsZXY(self): 46 | return (0,0,0) 47 | def mapQuadToUnitSquare(self, v1,v2,v3,v4,v5,v6,v7,v8,v9): 48 | pass 49 | def mapUnitSquareToQuad(self, v1,v2,v3,v4,v5,v6,v7,v8,v9): 50 | pass 51 | def ntransform(self, vector): 52 | return Vector3() 53 | def projection(self, v1, v2, v3, bol): 54 | pass 55 | def scaleAndRotationOnly(self): 56 | pass 57 | def skewXY(self): 58 | pass 59 | def translate(self): 60 | pass 61 | def transpose(self): 62 | pass 63 | def translationOnly(self): 64 | pass 65 | def vtransform(self, vector): 66 | return Vector3 67 | def xAxis(self): 68 | return Vector3 69 | def yAxis(self): 70 | return Vector3 71 | def zAxis(self): 72 | return Vector3 73 | 74 | class Vector2(object): 75 | def __init__(self, x=0, y=0): 76 | pass 77 | def cross(self, vetor2): 78 | return Vector2() 79 | def distanceBetween(self): 80 | pass 81 | def distanceSquared(self): 82 | pass 83 | def dot(self): 84 | return 0 85 | def length(self): 86 | pass 87 | def lengthSquared(self): 88 | pass 89 | def negate(self): 90 | pass 91 | def normalize(self): 92 | pass 93 | def set(self): 94 | pass 95 | def x(self): 96 | pass 97 | def y(self): 98 | pass 99 | 100 | class Vector3(Vector2): 101 | def __init__(self, x=0, y=0, z=0): 102 | super(Vector3, self).__init__() 103 | pass 104 | def distanceFromPlane(self): 105 | pass 106 | def fast_normalize(self): 107 | pass 108 | def maximum(self): 109 | pass 110 | def minimum(self): 111 | pass 112 | def reflect(self): 113 | pass 114 | def cross(self, vector3): 115 | return Vector3() 116 | 117 | class Vector4(object): 118 | def x(self): 119 | pass 120 | def y(self): 121 | pass 122 | def z(self): 123 | pass 124 | def w(self): 125 | pass 126 | 127 | class Quaternion(object): 128 | s=0.0 129 | vx=0.0 130 | vy=0.0 131 | vz=0.0 132 | def addInverse(self): 133 | pass 134 | def conjugate(self): 135 | pass 136 | def magnitude(self): 137 | pass 138 | def matrix(self): 139 | return Matrix4() 140 | def multInverse(self): 141 | return Quaternion() 142 | def set(self): 143 | pass 144 | def slerp(self): 145 | pass 146 | -------------------------------------------------------------------------------- /multi_script_editor/managers/nuke/rotopaint.py: -------------------------------------------------------------------------------- 1 | from main import Knob 2 | 3 | 4 | class AnimAttributes(object): 5 | pass 6 | class AnimCTransform(object): 7 | pass 8 | class AnimControlPoint(object): 9 | pass 10 | class AnimCurve(object): 11 | pass 12 | class AnimCurveKey(object): 13 | pass 14 | class AnimCurveViews(object): 15 | pass 16 | class BaseCurve(object): 17 | pass 18 | class CMatrix4(object): 19 | pass 20 | class CTransform(object): 21 | pass 22 | class CVec2(object): 23 | pass 24 | class CVec3(object): 25 | pass 26 | class CVec4(object): 27 | pass 28 | class ControlPoint(object): 29 | pass 30 | class CubicCurve(object): 31 | pass 32 | class CurveKnob(Knob): 33 | curveWidget=None 34 | rootLayer=None 35 | def changed(self): 36 | """ 37 | Call this after performing updates on the tree, to tell it that it's been updated. For many operations this is called automatically, but you can call it manually for those cases where it isn't. 38 | 39 | Returns: None 40 | """ 41 | def toElement(self, path): 42 | """ 43 | Takes a path which identifies a particular element in the curve tree and returns the corresponding Layer, Stroke or Shape object. The path is a slash separated string and is always resolved relative to the root layer. So if, for example, you have a RotoPaint node with a layer called 'Layer1' which contains a shape called 'Shape1', the path to the shape would be 'Layer1/Shape1'. >>> knob = nuke.toNode('RotoPaint1)['curves'] >>> shape = knob.toElement('Layer1/Shape1') >>> shape.name 'Shape1' 44 | 45 | Returns: Element 46 | """ 47 | 48 | class RotoKnob(CurveKnob): 49 | pass 50 | class CurveType(object): 51 | pass 52 | class Element(object): 53 | pass 54 | class ExtrapolationType(object): 55 | pass 56 | class Flag(object): 57 | pass 58 | class FlagType(object): 59 | pass 60 | class Hash(object): 61 | pass 62 | class InterpolationType(object): 63 | pass 64 | class Layer(object): 65 | pass 66 | class RotationOrder(object): 67 | pass 68 | class Shape(object): 69 | pass 70 | class ShapeControlPoint(object): 71 | pass 72 | class Stroke(object): 73 | pass 74 | class TransformOrder(object): 75 | pass 76 | 77 | def convertDirectoryToNuke6(): 78 | pass 79 | def convertDirectoryToNuke7(): 80 | pass 81 | def convertToNuke6(): 82 | pass 83 | def convertToNuke7(): 84 | pass 85 | -------------------------------------------------------------------------------- /multi_script_editor/managers/run_3dsmax.py: -------------------------------------------------------------------------------- 1 | import multi_script_editor 2 | reload(multi_script_editor) 3 | multi_script_editor.show3DSMax() -------------------------------------------------------------------------------- /multi_script_editor/run.cmd: -------------------------------------------------------------------------------- 1 | start c:\python27\python.exe scriptEditor.py %* -------------------------------------------------------------------------------- /multi_script_editor/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CURRENT=`dirname $(readlink -f $0)` 3 | python "$CURRENT/scriptEditor.py" 4 | -------------------------------------------------------------------------------- /multi_script_editor/sessionManager.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import json 3 | import os 4 | import codecs 5 | import settingsManager 6 | 7 | sessionFilename = 'pw_scriptEditor_session.json' 8 | 9 | 10 | class sessionManagerClass(object): 11 | def __init__(self): 12 | self.path = os.path.normpath(os.path.join(settingsManager.userPrefFolder(), sessionFilename)) 13 | if not os.path.exists(self.path): 14 | f = open(self.path, 'w') 15 | f.write('{}') 16 | f.close() 17 | 18 | def readSession(self): 19 | if os.path.exists(self.path): 20 | with codecs.open(self.path, "r", "utf-16") as stream: 21 | try: 22 | return json.load(stream) 23 | except: 24 | return [] 25 | return [] 26 | 27 | def writeSession(self, data): 28 | with codecs.open(self.path, "w", "utf-16") as stream: 29 | json.dump(data, stream, indent=4) 30 | return self.path 31 | -------------------------------------------------------------------------------- /multi_script_editor/settingsManager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import codecs 4 | from managers import context 5 | 6 | settingsFilename = 'pw_scriptEditor_pref.json' 7 | 8 | def userPrefFolder(): 9 | appData = None 10 | if context == 'hou': 11 | appData = os.getenv('HOUDINI_USER_PREF_DIR') 12 | elif context == 'maya': 13 | appData = os.getenv('MAYA_APP_DIR') 14 | elif context == 'nuke': 15 | appData = os.path.join(os.environ['HOME'], '.nuke') 16 | elif context == 'max': 17 | import MaxPlus 18 | appData = os.path.dirname(MaxPlus.PathManager.GetTempDir()) 19 | if not appData: 20 | appData = os.getenv('HOME') or os.path.expanduser('~') 21 | return appData 22 | 23 | def settingsFile(): 24 | path = os.path.normpath(os.path.join(userPrefFolder(), settingsFilename)).replace('\\','/') 25 | if not os.path.exists(path): 26 | with open(path, 'w') as f: 27 | f.write('[]') 28 | return path 29 | 30 | 31 | class scriptEditorClass(object): 32 | def __init__(self): 33 | super(scriptEditorClass, self).__init__() 34 | self.path = settingsFile() 35 | 36 | def readSettings(self): 37 | if os.path.exists(self.path) and os.path.isfile(self.path): 38 | with codecs.open(self.path, "r", "utf-16") as stream: 39 | try: 40 | return json.load(stream) 41 | except: 42 | return self.defaults() 43 | return self.defaults() 44 | 45 | def writeSettings(self, data): 46 | with codecs.open(self.path, "w", "utf-16") as stream: 47 | json.dump(data, stream, indent=4) 48 | 49 | def defaults(self): 50 | return dict(geometry=None, 51 | outFontSize=8 52 | ) 53 | 54 | -------------------------------------------------------------------------------- /multi_script_editor/shortcuts.txt: -------------------------------------------------------------------------------- 1 | Execute selected > CTRL + ENTER 2 | Execute All > CTRL + SHIFT + ENTER 3 | Indent Selection > TAB 4 | Unindent Selection > SHIFT + TAB 5 | Activate Completer > UP or DOWN 6 | Deactivate Completer > BACKSPACE or ane char 7 | Hide Completer > ESC 8 | Autocomplete code > ENTER (in Completer) 9 | Autocomplete first > ENTER or TAB in Editor 10 | Font Size > CTRL + MouseWheel 11 | Scroll Code Left-Right > ALT + MouseWheel 12 | Duplicate line or selected text > CTRL + D 13 | Comment/Uncomment line or selected lines > ALT + Q 14 | Undo > CTRL + Z 15 | Redo > CTRL + Y 16 | Copy > CTRL + C 17 | Paste > CTRL + V 18 | Cut > CTRL + X 19 | Find and Replace > CTRL + F 20 | Wrap dropped nodes (Maya and Houdini) > MMB Drag + ALT -------------------------------------------------------------------------------- /multi_script_editor/style/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/style/__init__.py -------------------------------------------------------------------------------- /multi_script_editor/style/completer.qss: -------------------------------------------------------------------------------- 1 | /*********** LIST WIDGET**************/ 2 | QAbstractItemView 3 | { 4 | color: rgb[completer_text]; 5 | background-color: rgb[completer_background]; 6 | alternate-background-color: rgb[completer_alt_background]; 7 | height: 25px; 8 | font: [textsize]pt; 9 | } 10 | QListView::item:selected{ 11 | background: rgb[completer_selected_background]; 12 | } 13 | 14 | 15 | QListView:item:hover 16 | { 17 | background-color:rgb[completer_hover_background]; 18 | color: rgb[completer_text]; 19 | } 20 | 21 | /***********SCROLLBAR***************/ 22 | 23 | QScrollBar::handle:vertical 24 | { 25 | background: #565656; 26 | min-height: 20px; 27 | border-radius: 2px; 28 | } 29 | QScrollBar::handle:vertical:pressed 30 | { 31 | background: #5b5a5a; 32 | } 33 | 34 | QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical 35 | { 36 | border: 1px solid #1b1b19; 37 | border-radius: 1px; 38 | background: #565656; 39 | height: 14px; 40 | subcontrol-origin: margin; 41 | } 42 | QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed 43 | { 44 | background: #5b5a5a; 45 | } 46 | 47 | QScrollBar::sub-line:vertical 48 | { 49 | subcontrol-position: top; 50 | } 51 | QScrollBar::add-line:vertical 52 | { 53 | subcontrol-position: bottom; 54 | } 55 | 56 | QScrollBar:vertical 57 | { 58 | background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0.0 #121212, stop: 0.2 #282828, stop: 1 #484848); 59 | width: 15px; 60 | margin: 16px 0 16px 0; 61 | border: 1px solid #222222; 62 | } 63 | 64 | QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical 65 | { 66 | border: 0px; 67 | width: 10px; 68 | height: 10px; 69 | background: #565656; 70 | } 71 | QScrollBar::up-arrow:vertical:pressed, QScrollBar::down-arrow:vertical:pressed 72 | { 73 | background: #5b5a5a; 74 | } 75 | 76 | QScrollBar::up-arrow:vertical 77 | { 78 | border-image: url(:/arrow_up.png) 1; 79 | } 80 | QScrollBar::down-arrow:vertical 81 | { 82 | border-image: url(:/arrow_down.png) 1; 83 | } 84 | 85 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical 86 | { 87 | background: none; 88 | } 89 | 90 | QTextEdit{ 91 | background: rgb[background]; 92 | } -------------------------------------------------------------------------------- /multi_script_editor/style/links.py: -------------------------------------------------------------------------------- 1 | links = dict( 2 | donate='', 3 | tutorials='http://paulwinex.com/portfolio/multi-script-editor/', 4 | manual='https://github.com/paulwinex/pw_MultiScriptEditor', 5 | site='http://www.paulwinex.com/' 6 | ) -------------------------------------------------------------------------------- /multi_script_editor/style/pw.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/style/pw.ico -------------------------------------------------------------------------------- /multi_script_editor/style/pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/style/pw.png -------------------------------------------------------------------------------- /multi_script_editor/style/script_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/style/script_editor.png -------------------------------------------------------------------------------- /multi_script_editor/tested.txt: -------------------------------------------------------------------------------- 1 | Supported applications: 2 | Maya 3 | Houdini 4 | Nuke 5 | 3DsMax 6 | 7 | Tested on: 8 | Windows 7x64 9 | Windows 10x64 10 | Ubuntu 12.04 11 | Ubuntu 14.04 12 | Ubuntu 16.04 13 | 14 | Maya 2018 15 | Maya 2017 16 | Maya 2016 17 | Maya 2015 18 | Maya 2014 19 | Maya 2013 20 | Houdini 13 21 | Houdini 14 22 | Houdini 15 23 | Nuke 8 24 | Nuke 9 25 | Nuke 10 26 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/widgets/__init__.py -------------------------------------------------------------------------------- /multi_script_editor/widgets/about.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import icons 9 | import about_UIs 10 | import os 11 | 12 | class aboutClass(QDialog, about_UIs.Ui_Dialog): 13 | def __init__(self, parent): 14 | super(aboutClass, self).__init__(parent) 15 | self.setupUi(self) 16 | self.title_lb.setText(self.title_lb.text()+str(parent.ver)) 17 | self.text_link_lb.setText(text) 18 | self.icon_lb.setPixmap(QPixmap(icons.icons['pw']).scaled(60,60, Qt.KeepAspectRatio, Qt.SmoothTransformation)) 19 | self.donate_btn.setMinimumHeight(35) 20 | self.donate_btn.setIconSize(QSize(24,24)) 21 | self.donate_btn.setIcon(QIcon(icons.icons['donate'])) 22 | self.donate_btn.clicked.connect(lambda :parent.openLink('donate')) 23 | self.donate_btn.hide() 24 | testedFile = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'tested.txt') 25 | if os.path.exists(testedFile): 26 | outText = open(testedFile).read() 27 | self.textBrowser.setPlainText(outText) 28 | 29 | 30 | text = '''Paul Winex 2018 31 | Any question or bug report: paulwinex@gmail.com 32 | ''' -------------------------------------------------------------------------------- /multi_script_editor/widgets/about.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 465 10 | 393 11 | 12 | 13 | 14 | About Multi Script Editor 15 | 16 | 17 | 18 | 19 | 20 | 20 21 | 22 | 23 | 20 24 | 25 | 26 | 27 | 28 | Qt::Horizontal 29 | 30 | 31 | 32 | 40 33 | 20 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 20 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 20 55 | 56 | 57 | 58 | Multi Script Editor v 59 | 60 | 61 | 62 | 63 | 64 | 65 | Qt::Horizontal 66 | 67 | 68 | 69 | 40 70 | 20 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Paul Winex 2015 81 | 82 | 83 | 84 | 85 | 86 | 87 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 88 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 89 | p, li { white-space: pre-wrap; } 90 | </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> 91 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">File not Found :(</span></p></body></html> 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Donate 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/about_UIs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'D:\Dropbox\Dropbox\pw_prefs\RnD\tools\pw_scriptEditor\multi_script_editor\widgets\about.ui' 4 | # 5 | # Created: Thu Apr 02 22:56:45 2015 6 | # by: pyside-uic 0.2.15 running on PySide 1.2.2 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | try: 11 | from PySide.QtCore import * 12 | from PySide.QtGui import * 13 | except: 14 | from PySide2.QtCore import * 15 | from PySide2.QtGui import * 16 | from PySide2.QtWidgets import * 17 | 18 | class Ui_Dialog(object): 19 | def setupUi(self, Dialog): 20 | Dialog.setObjectName("Dialog") 21 | Dialog.resize(465, 393) 22 | self.verticalLayout = QVBoxLayout(Dialog) 23 | self.verticalLayout.setObjectName("verticalLayout") 24 | self.horizontalLayout = QHBoxLayout() 25 | self.horizontalLayout.setContentsMargins(-1, 20, -1, 20) 26 | self.horizontalLayout.setObjectName("horizontalLayout") 27 | spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 28 | self.horizontalLayout.addItem(spacerItem) 29 | self.icon_lb = QLabel(Dialog) 30 | font = QFont() 31 | font.setPointSize(20) 32 | self.icon_lb.setFont(font) 33 | self.icon_lb.setText("") 34 | self.icon_lb.setObjectName("icon_lb") 35 | self.horizontalLayout.addWidget(self.icon_lb) 36 | self.title_lb = QLabel(Dialog) 37 | font = QFont() 38 | font.setPointSize(20) 39 | self.title_lb.setFont(font) 40 | self.title_lb.setObjectName("title_lb") 41 | self.horizontalLayout.addWidget(self.title_lb) 42 | spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 43 | self.horizontalLayout.addItem(spacerItem1) 44 | self.verticalLayout.addLayout(self.horizontalLayout) 45 | self.text_link_lb = QLabel(Dialog) 46 | self.text_link_lb.setObjectName("text_link_lb") 47 | self.verticalLayout.addWidget(self.text_link_lb) 48 | self.textBrowser = QTextBrowser(Dialog) 49 | self.textBrowser.setObjectName("textBrowser") 50 | self.verticalLayout.addWidget(self.textBrowser) 51 | self.horizontalLayout_2 = QHBoxLayout() 52 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 53 | self.donate_btn = QPushButton(Dialog) 54 | self.donate_btn.setObjectName("donate_btn") 55 | self.horizontalLayout_2.addWidget(self.donate_btn) 56 | self.verticalLayout.addLayout(self.horizontalLayout_2) 57 | self.verticalLayout.setStretch(2, 1) 58 | 59 | self.retranslateUi(Dialog) 60 | QMetaObject.connectSlotsByName(Dialog) 61 | 62 | def retranslateUi(self, Dialog): 63 | Dialog.setWindowTitle(QApplication.translate("Dialog", "About Multi Script Editor", None)) 64 | self.title_lb.setText(QApplication.translate("Dialog", "Multi Script Editor v", None)) 65 | self.text_link_lb.setText(QApplication.translate("Dialog", "Paul Winex 2015", None)) 66 | self.textBrowser.setHtml(QApplication.translate("Dialog", "\n" 67 | "\n" 70 | "

File not Found :(

", None)) 71 | self.donate_btn.setText(QApplication.translate("Dialog", "Donate", None)) 72 | 73 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/completeWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import os, re 9 | from . pythonSyntax import design 10 | import managers 11 | style = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'style', 'completer.qss') 12 | if not os.path.exists(style): 13 | style=None 14 | 15 | 16 | class completeMenuClass(QListWidget): 17 | def __init__(self, parent=None, editor=None): 18 | # if managers.context == 'hou': 19 | # super(completeMenuClass, self).__init__(managers.main_parent or parent) 20 | # else: 21 | super(completeMenuClass, self).__init__(parent) 22 | self.setAlternatingRowColors(1) 23 | self.lineHeight = 18 24 | self.e = editor 25 | self.setAttribute(Qt.WA_ShowWithoutActivating) 26 | if managers._s == 'w': 27 | self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window) 28 | else: 29 | self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window | Qt.WindowStaysOnTopHint) 30 | @self.itemDoubleClicked.connect 31 | def insertSelected(item): 32 | if item: 33 | comp = item.data(32) 34 | self.sendText(comp) 35 | self.hideMe() 36 | 37 | def updateStyle(self, colors=None): 38 | text = design.editorStyle() 39 | self.setStyleSheet(text) 40 | 41 | def updateCompleteList(self, lines=None, extra=None): 42 | self.clear() 43 | if lines or extra: 44 | self.showMe() 45 | if lines: 46 | for i in [x for x in lines if not x.name == 'mro']: 47 | item = QListWidgetItem(i.name) 48 | item.setData(32, i) 49 | self.addItem(item) 50 | if extra: 51 | 52 | font = self.font() 53 | font.setItalic(1) 54 | font.setPointSize(font.pointSize()*0.8) 55 | for e in extra: 56 | item = QListWidgetItem(e.name) 57 | item.setData(32, e) 58 | item.setFont(font) 59 | self.addItem(item) 60 | 61 | font = QFont("monospace", self.lineHeight, False) 62 | fm = QFontMetrics (font) 63 | width = fm.width(' ') * max([len(x.name) for x in lines or extra]) + 40 64 | 65 | self.resize(max(250,width), 250) 66 | self.setCurrentRow(0) 67 | else: 68 | self.hideMe() 69 | 70 | def applyCurrentComplete(self): 71 | i = self.selectedItems() 72 | if i: 73 | comp = i[0].data(32) 74 | self.sendText(comp) 75 | self.hideMe() 76 | 77 | def keyPressEvent(self, event): 78 | if event.key() == Qt.Key_Escape: 79 | self.close() 80 | # elif event.text(): 81 | # self.editor().setFocus() 82 | elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: 83 | self.editor().setFocus() 84 | self.applyCurrentComplete() 85 | return event 86 | elif event.key() == Qt.Key_Up: 87 | sel = self.selectedItems() 88 | if sel: 89 | i = self.row(sel[0]) 90 | if i == 0: 91 | QListWidget.keyPressEvent(self, event) 92 | self.setCurrentRow(self.count()-1) 93 | return 94 | elif event.key() == Qt.Key_Down: 95 | sel = self.selectedItems() 96 | if sel: 97 | i = self.row(sel[0]) 98 | if i+1 == self.count(): 99 | QListWidget.keyPressEvent(self, event) 100 | self.setCurrentRow(0) 101 | return 102 | elif event.key() == Qt.Key_Backspace: 103 | self.editor().setFocus() 104 | self.editor().activateWindow() 105 | elif event.text(): 106 | self.editor().keyPressEvent(event) 107 | return 108 | 109 | QListWidget.keyPressEvent(self, event) 110 | 111 | def sendText(self, comp): 112 | self.editor().insertText(comp) 113 | 114 | def editor(self): 115 | return self.e 116 | 117 | def activateCompleter(self, key=False): 118 | self.activateWindow() 119 | if not key==Qt.Key_Up: 120 | self.setCurrentRow(min(1, self.count()-1)) 121 | else: 122 | self.setCurrentRow(self.count()-1) 123 | 124 | def showMe(self): 125 | self.show() 126 | self.e.moveCompleter() 127 | 128 | def hideMe(self): 129 | self.hide() 130 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/findWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import findWidget_UIs as ui 9 | 10 | class findWidgetClass(QWidget, ui.Ui_findReplace): 11 | searchSignal = Signal(str) 12 | replaceSignal = Signal(list) 13 | replaceAllSignal = Signal(list) 14 | def __init__(self, parent): 15 | super(findWidgetClass, self).__init__(parent) 16 | self.setupUi(self) 17 | self.setWindowFlags(Qt.Tool) 18 | center = parent.parent().mapToGlobal(parent.geometry().center()) 19 | myGeo = self.geometry() 20 | myGeo.moveCenter(center) 21 | self.setGeometry(myGeo) 22 | self.find_le.setFocus() 23 | #connect 24 | self.find_btn.clicked.connect(self.search) 25 | self.find_le.returnPressed.connect(self.search) 26 | self.replace_btn.clicked.connect(self.replace) 27 | self.replace_le.returnPressed.connect(self.replace) 28 | self.replaceAll_btn.clicked.connect(self.replaceAll) 29 | 30 | def search(self): 31 | self.searchSignal.emit(self.find_le.text()) 32 | QTimer.singleShot(10, self.find_le.setFocus) 33 | 34 | def replace(self): 35 | find = self.find_le.text() 36 | rep = self.replace_le.text() 37 | self.replaceSignal.emit([find, rep]) 38 | QTimer.singleShot(10, self.replace_le.setFocus) 39 | 40 | def replaceAll(self): 41 | find = self.find_le.text() 42 | rep = self.replace_le.text() 43 | self.replaceAllSignal.emit([find, rep]) 44 | QTimer.singleShot(10, self.replace_le.setFocus) 45 | 46 | def keyPressEvent(self, event): 47 | if event.key() == Qt.Key_Escape: 48 | self.close() 49 | super(findWidgetClass, self).keyPressEvent(event) -------------------------------------------------------------------------------- /multi_script_editor/widgets/findWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | findReplace 4 | 5 | 6 | 7 | 0 8 | 0 9 | 246 10 | 101 11 | 12 | 13 | 14 | Find and Replace 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Find 29 | 30 | 31 | 32 | 33 | 34 | 35 | Replace 36 | 37 | 38 | 39 | 40 | 41 | 42 | Replace All 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | find_le 52 | replace_le 53 | find_btn 54 | replace_btn 55 | replaceAll_btn 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/findWidget_UIs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | # Created: Thu Apr 02 15:51:34 2015 5 | # by: pyside-uic 0.2.15 running on PySide 1.2.2 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | try: 10 | from PySide.QtCore import * 11 | from PySide.QtGui import * 12 | except: 13 | from PySide2.QtCore import * 14 | from PySide2.QtGui import * 15 | from PySide2.QtWidgets import * 16 | 17 | 18 | class Ui_findReplace(object): 19 | def setupUi(self, findReplace): 20 | findReplace.setObjectName("findReplace") 21 | findReplace.resize(246, 101) 22 | self.verticalLayout = QVBoxLayout(findReplace) 23 | self.verticalLayout.setObjectName("verticalLayout") 24 | self.gridLayout = QGridLayout() 25 | self.gridLayout.setObjectName("gridLayout") 26 | self.replace_le = QLineEdit(findReplace) 27 | self.replace_le.setObjectName("replace_le") 28 | self.gridLayout.addWidget(self.replace_le, 1, 0, 1, 1) 29 | self.find_le = QLineEdit(findReplace) 30 | self.find_le.setObjectName("find_le") 31 | self.gridLayout.addWidget(self.find_le, 0, 0, 1, 1) 32 | self.find_btn = QPushButton(findReplace) 33 | self.find_btn.setObjectName("find_btn") 34 | self.gridLayout.addWidget(self.find_btn, 0, 1, 1, 1) 35 | self.replace_btn = QPushButton(findReplace) 36 | self.replace_btn.setObjectName("replace_btn") 37 | self.gridLayout.addWidget(self.replace_btn, 1, 1, 1, 1) 38 | self.replaceAll_btn = QPushButton(findReplace) 39 | self.replaceAll_btn.setObjectName("replaceAll_btn") 40 | self.gridLayout.addWidget(self.replaceAll_btn, 2, 1, 1, 1) 41 | self.verticalLayout.addLayout(self.gridLayout) 42 | 43 | self.retranslateUi(findReplace) 44 | QMetaObject.connectSlotsByName(findReplace) 45 | findReplace.setTabOrder(self.find_le, self.replace_le) 46 | findReplace.setTabOrder(self.replace_le, self.find_btn) 47 | findReplace.setTabOrder(self.find_btn, self.replace_btn) 48 | findReplace.setTabOrder(self.replace_btn, self.replaceAll_btn) 49 | 50 | def retranslateUi(self, findReplace): 51 | findReplace.setWindowTitle(QApplication.translate("findReplace", "Find and Replace", None)) 52 | self.find_btn.setText(QApplication.translate("findReplace", "Find", None)) 53 | self.replace_btn.setText(QApplication.translate("findReplace", "Replace", None)) 54 | self.replaceAll_btn.setText(QApplication.translate("findReplace", "Replace All", None)) 55 | 56 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/numBarWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import managers 9 | 10 | class lineNumberBarClass(QWidget): 11 | def __init__(self, edit, parent=None): 12 | QWidget.__init__(self, parent) 13 | self.edit = edit 14 | self.highest_line = 0 15 | self.setMinimumWidth(30) 16 | # self.edit.installEventFilter(self) 17 | # self.edit.viewport().installEventFilter(self) 18 | self.bg = None 19 | 20 | def update(self, *args): 21 | ''' 22 | Updates the number bar to display the current set of numbers. 23 | Also, adjusts the width of the number bar if necessary. 24 | ''' 25 | # The + 4 is used to compensate for the current line being bold. 26 | if managers.context == 'hou': 27 | fontSize = self.edit.fs 28 | else: 29 | fontSize = self.edit.font().pointSize() 30 | width = ((self.fontMetrics().width(str(self.highest_line)) + 7))*(fontSize/13.0) 31 | if self.width() != width and width > 10: 32 | self.setFixedWidth(width) 33 | bg = self.palette().brush(QPalette.Normal,QPalette.Window).color().toHsv() 34 | v = bg.value() 35 | if v > 20: 36 | v = int(bg.value()*0.8) 37 | else: 38 | v = int(bg.value()*1.1) 39 | self.bg = QColor.fromHsv(bg.hue(), bg.saturation(), v) 40 | QWidget.update(self, *args) 41 | 42 | def paintEvent(self, event): 43 | contents_y = self.edit.verticalScrollBar().value() 44 | page_bottom = contents_y + self.edit.viewport().height() 45 | font_metrics = self.fontMetrics() 46 | current_block = self.edit.document().findBlock(self.edit.textCursor().position()) 47 | painter = QPainter(self) 48 | line_count = 0 49 | # Iterate over all text blocks in the document. 50 | block = self.edit.document().begin() 51 | if managers.context == 'hou': 52 | fontSize = self.edit.fs 53 | font = QFont('monospace', fontSize*0.7) 54 | offset = (font_metrics.ascent() + font_metrics.descent())/2 55 | else: 56 | fontSize = self.edit.font().pointSize() 57 | font = painter.font() 58 | font.setPixelSize(fontSize) 59 | offset = font_metrics.ascent() + font_metrics.descent() 60 | color = painter.pen().color() 61 | painter.setFont(font) 62 | align = Qt.AlignRight 63 | while block.isValid(): 64 | line_count += 1 65 | # The top left position of the block in the document 66 | position = self.edit.document().documentLayout().blockBoundingRect(block).topLeft() 67 | # Check if the position of the block is out side of the visible 68 | # area. 69 | if position.y() == page_bottom: 70 | break 71 | 72 | rec = QRect(0, 73 | round(position.y()) - contents_y, 74 | self.width()-5, 75 | fontSize + offset) 76 | 77 | # draw line rect 78 | if block == current_block: 79 | painter.setPen(Qt.NoPen) 80 | painter.setBrush(QBrush(self.bg)) 81 | painter.drawRect(QRect(0, 82 | round(position.y()) - contents_y, 83 | self.width(), 84 | fontSize + (offset/2) )) 85 | # #restore color 86 | painter.setPen(QPen(color)) 87 | 88 | # draw text 89 | painter.drawText(rec, align, str(line_count)) 90 | # control points 91 | 92 | block = block.next() 93 | self.highest_line = line_count 94 | painter.end() 95 | QWidget.paintEvent(self, event) 96 | 97 | def eventFilter(self, object, event): 98 | # Update the line numbers for all events on the text edit and the viewport. 99 | # This is easier than connecting all necessary singals. 100 | if object in (self.edit, self.edit.viewport()): 101 | self.update() 102 | return False 103 | return QWidget.eventFilter(object, event) -------------------------------------------------------------------------------- /multi_script_editor/widgets/outputWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | 9 | from managers import context 10 | font_name = 'Courier' 11 | 12 | 13 | class outputClass(QTextBrowser): 14 | def __init__(self): 15 | super(outputClass, self).__init__() 16 | self.setWordWrapMode(QTextOption.NoWrap) 17 | font = QFont("Courier") 18 | font.setStyleHint(QFont.Monospace) 19 | font.setFixedPitch(True) 20 | self.setFont(font) 21 | self.fs = 14 22 | self.document().setDefaultFont(QFont(font_name, self.fs, QFont.Monospace)) 23 | metrics = QFontMetrics(self.document().defaultFont()) 24 | self.setTabStopWidth(4 * metrics.width(' ')) 25 | self.setMouseTracking(1) 26 | 27 | def showMessage(self, msg): 28 | self.moveCursor(QTextCursor.End) 29 | cursor = self.textCursor() 30 | cursor.insertText(str(msg)+'\n') 31 | self.setTextCursor(cursor) 32 | self.moveCursor(QTextCursor.End) 33 | self.ensureCursorVisible() 34 | 35 | def setTextEditFontSize(self, size): 36 | style = '''QTextEdit 37 | { 38 | font-size: %spx; 39 | }''' % size 40 | self.setStyleSheet(style) 41 | 42 | 43 | def wheelEvent(self, event): 44 | if event.modifiers() == Qt.ControlModifier: 45 | if event.delta() > 0: 46 | self.changeFontSize(True) 47 | else: 48 | self.changeFontSize(False) 49 | # super(outputClass, self).wheelEvent(event) 50 | QTextBrowser.wheelEvent(self, event) 51 | 52 | def changeFontSize(self, up): 53 | if context == 'hou': 54 | if up: 55 | self.fs = min(30, self.fs+1) 56 | else: 57 | self.fs = max(8, self.fs - 1) 58 | self.setTextEditFontSize(self.fs) 59 | else: 60 | f = self.font() 61 | size = f.pointSize() 62 | if up: 63 | size = min(30, size+1) 64 | else: 65 | size = max(8, size - 1) 66 | f.setPointSize(size) 67 | self.setFont(f) 68 | 69 | 70 | # def mousePressEvent(self, event): 71 | # print context 72 | # if context == 'hou': 73 | # if event.button() == Qt.LeftButton: 74 | # # super(outputClass, self).mousePressEvent(event) 75 | # QTextBrowser.mousePressEvent(self, event) 76 | # else: 77 | # QTextBrowser.mousePressEvent(self, event) -------------------------------------------------------------------------------- /multi_script_editor/widgets/pythonSyntax/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulwinex/pw_MultiScriptEditor/e447e99f87cb07e238baf693b7e124e50efdbc51/multi_script_editor/widgets/pythonSyntax/__init__.py -------------------------------------------------------------------------------- /multi_script_editor/widgets/pythonSyntax/design.py: -------------------------------------------------------------------------------- 1 | import settingsManager 2 | import os, re 3 | 4 | EditorStyle = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'style', 'completer.qss') 5 | if not os.path.exists(EditorStyle): 6 | EditorStyle=None 7 | 8 | defaultColors = dict( 9 | background = (40,40,40), 10 | keywords = (65,255,130), 11 | digits = (250,255,62), 12 | definition = (255,160,250), 13 | operator = (230, 220, 110), 14 | extra = (110,180,230), 15 | methods = (120, 190, 205), 16 | comment = (110,100,100), 17 | string = (245,165,18), 18 | docstring = (130,160,75), 19 | boolean = (160,220,120), 20 | brace = (235,235,195), 21 | completer_text=(200,200,200), 22 | completer_selected_text= (105,105,105), 23 | completer_hover_text= (255,255,255), 24 | completer_background=(59,59,59), 25 | completer_alt_background= (65,65,65), 26 | completer_hover_background= (85,85,85), 27 | completer_selected_background= (123,123,123), 28 | default=(210,210,210) 29 | ) 30 | 31 | 32 | def getColors(theme=False): 33 | s = settingsManager.scriptEditorClass() 34 | settings = s.readSettings() 35 | if not theme: 36 | theme = settings.get('theme') 37 | result = {k:v for k,v in defaultColors.items()} 38 | if theme: 39 | if 'colors' in settings: 40 | colors = settings['colors'].get(theme) 41 | if colors: 42 | for k, v in colors.items(): 43 | result[k] = v 44 | return result 45 | 46 | 47 | def editorStyle(theme=None): 48 | colors = getColors(theme) 49 | colors = {k:tuple(v) if isinstance(v, list) else v for k,v in colors.items()} 50 | if EditorStyle: 51 | text = open(EditorStyle).read() 52 | proxys = re.findall('\[.*\]', text) 53 | for p in proxys: 54 | name = p[1:-1] 55 | if name in colors: 56 | text = text.replace(p, str(colors[name])) 57 | return text 58 | 59 | def applyColorToEditorStyle(colors=None): 60 | if EditorStyle: 61 | text = open(EditorStyle).read() 62 | proxys = re.findall('\[.*\]', text) 63 | for p in proxys: 64 | name = p[1:-1] 65 | if name in colors: 66 | c = colors[name] 67 | if isinstance(c, list): 68 | c = tuple(c) 69 | text = text.replace(p, str(c)) 70 | return text 71 | return '' 72 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/pythonSyntax/keywords.py: -------------------------------------------------------------------------------- 1 | syntax = {"extension": [ 2 | "py", "pyw"], 3 | 4 | "comment": ["#"], 5 | 6 | "string": ["\"", "'"], 7 | 8 | "operators": ["=", 9 | # Comparison 10 | "!=", "<", ">", 11 | # Arithmetic 12 | "+", "\-", "*", "/", "%", "*", 13 | # Bitwise 14 | "^", "|", "&"], 15 | "keywords": [ "and", 16 | "assert", 17 | "break", 18 | "continue", 19 | "del", 20 | "elif", 21 | "else", 22 | "except", 23 | "exec", 24 | "finally", 25 | "for", 26 | "from", 27 | "global", 28 | "if", 29 | "import", 30 | "in", 31 | "is", 32 | "not", 33 | "or", 34 | "pass", 35 | "print", 36 | "raise", 37 | "return", 38 | "try", 39 | "while", 40 | "yield", 41 | "None", 42 | "with" 43 | ], 44 | "definition":["class", 45 | "def", 46 | "lambda"], 47 | "boolean" : ["True", 48 | "False"], 49 | "extras": [ 50 | "abs", 51 | "all", 52 | "any", 53 | "basestring", 54 | "bin", 55 | "bool", 56 | "bytearray", 57 | "callable", 58 | "chr", 59 | "classmethod", 60 | "cmp", 61 | "compile", 62 | "complex", 63 | "delattr", 64 | "dict", 65 | "dir", 66 | "divmod", 67 | "enumerate", 68 | "eval", 69 | "execfile", 70 | "file", 71 | "filter", 72 | "float", 73 | "format", 74 | "frozenset", 75 | "getattr", 76 | "globals", 77 | "hasattr", 78 | "hash", 79 | "help", 80 | "hex", 81 | "id", 82 | "input", 83 | "int", 84 | "isinstance", 85 | "issubclass", 86 | "iter", 87 | "len", 88 | "list", 89 | "locals", 90 | "long", 91 | "map", 92 | "max", 93 | "memoryview", 94 | "min", 95 | "next", 96 | "object", 97 | "oct", 98 | "open", 99 | "ord", 100 | "pow", 101 | "property", 102 | "range", 103 | "raw_input", 104 | "reduce", 105 | "reload", 106 | "repr", 107 | "reversed", 108 | "round", 109 | "set", 110 | "setattr", 111 | "slice", 112 | "sorted", 113 | "staticmethod", 114 | "str", 115 | "sum", 116 | "tuple", 117 | "type", 118 | "unichr", 119 | "unicode", 120 | "vars", 121 | "xrange", 122 | "zip", 123 | "apply", 124 | "buffer", 125 | "coerce", 126 | "intern" 127 | ], 128 | "braces" :['\{', '\}', '\(', '\)', '\[', '\]'] 129 | 130 | } 131 | 132 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/pythonSyntax/syntaxHighLighter.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import re 9 | import design 10 | import keywords 11 | 12 | 13 | class PythonHighlighterClass (QSyntaxHighlighter): 14 | def __init__(self, document, colors=None): 15 | QSyntaxHighlighter.__init__(self, document) 16 | 17 | if colors: 18 | self.colors = colors 19 | else: 20 | self.colors = design.getColors() 21 | 22 | # Multi line comments 23 | self.tri_single = (QRegExp("'''"), 1, self.getStyle(self.colors['docstring'])) 24 | self.tri_double = (QRegExp('"""'), 2, self.getStyle(self.colors['docstring'])) 25 | 26 | rules = [] 27 | # defaults 28 | # rules += [(r".*", 0, self.getStyle(self.colors['default'], False))] 29 | # Keywords 30 | rules += [('\\b%s\\b' % w, 0, self.getStyle(self.colors['keywords'], True)) 31 | for w in keywords.syntax['keywords']] 32 | # Methods 33 | rules += [("\\b[A-Za-z0-9_]+(?=\\()", 0, self.getStyle(self.colors['methods'], False))] 34 | # Operators 35 | rules += [(r'[~!@$%^&*()-+=]', 0, self.getStyle(self.colors['operator']))] 36 | # for o in pythonSyntax.syntax['operators']] 37 | #Braces 38 | rules += [(r'%s' % b, 0, self.getStyle(self.colors['brace'])) 39 | for b in keywords.syntax['braces']] 40 | # Definition 41 | rules += [("\\b%s\\b" % b, 0, self.getStyle(self.colors['definition'], True)) 42 | for b in keywords.syntax['definition']] 43 | # Extra 44 | rules += [("\\b%s\\b" % b, 0, self.getStyle(self.colors['extra'])) 45 | for b in keywords.syntax['extras']] 46 | 47 | # Comment 48 | # rules += [(r'#([.*]+|[^#]*)', 0, self.getStyle(design.comment))] 49 | 50 | # Digits 51 | rules += [(r"\b[\d]+\b", 0, self.getStyle(self.colors['digits']))] 52 | # ("(?:^|[^A-Za-z])([\d|\.]*\d+)", 0, self.getStyle(design.digits)), 53 | 54 | 55 | # Double-quoted string 56 | rules += [(r'[ru]?"[^"\\]*(\\.[^"\\]*)*"', 0, self.getStyle(self.colors['string']))] 57 | 58 | # Single-quoted string 59 | rules += [(r"[ru]?'[^'\\]*(\\.[^'\\]*)*'", 0, self.getStyle(self.colors['string']))] 60 | 61 | 62 | # Build a QRegExp for each pattern 63 | self.rules = [(QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] 64 | # self.rehighlight() 65 | 66 | 67 | def getStyle(self, color, bold=False): 68 | brush = QBrush( QColor(*color)) 69 | f = QTextCharFormat() 70 | if bold: 71 | f.setFontWeight( QFont.Bold ) 72 | f.setForeground( brush ) 73 | return f 74 | 75 | def highlightBlock(self, text): 76 | """Apply syntax highlighting to the given block of text. 77 | """ 78 | defFormat = self.getStyle(self.colors['default']) 79 | self.setFormat(0, len(text), defFormat) 80 | 81 | # Do other syntax formatting 82 | for expression, nth, format in self.rules: 83 | index = expression.indexIn(text, 0) 84 | # print expression, index 85 | while index >= 0: 86 | # We actually want the index of the nth match 87 | index = expression.pos(nth) 88 | length = len(expression.cap(nth)) 89 | self.setFormat(index, length, format) 90 | index = expression.indexIn(text, index + length) 91 | 92 | 93 | strings = re.findall(r'(".*?")|(\'.*?\')', text) 94 | if '#' in text: 95 | copy = text 96 | if strings: 97 | pat = [] 98 | for s in strings: 99 | for match in s: 100 | if match: 101 | pat.append(match) 102 | for s in pat: 103 | copy = copy.replace(s, '_'*len(s)) 104 | if '#' in copy: 105 | index = copy.index('#') 106 | length = len(copy) - index 107 | self.setFormat(index, length, self.getStyle(self.colors['comment'])) 108 | 109 | self.setCurrentBlockState(0) 110 | 111 | # Do multi-line strings 112 | in_multiline = self.match_multiline(text, *self.tri_single) 113 | if not in_multiline: 114 | in_multiline = self.match_multiline(text, *self.tri_double) 115 | 116 | 117 | def match_multiline(self, text, delimiter, in_state, style): 118 | """Do highlighting of multi-line strings. ``delimiter`` should be a 119 | ``QRegExp`` for triple-single-quotes or triple-double-quotes, and 120 | ``in_state`` should be a unique integer to represent the corresponding 121 | state changes when inside those strings. Returns True if we're still 122 | inside a multi-line string when this function is finished. """ 123 | # If inside triple-single quotes, start at 0 124 | if self.previousBlockState() == in_state: 125 | start = 0 126 | add = 0 127 | # Otherwise, look for the delimiter on this line 128 | else: 129 | start = delimiter.indexIn(text) 130 | # Move past this match 131 | add = delimiter.matchedLength() 132 | 133 | # As long as there's a delimiter match on this line... 134 | while start >= 0: 135 | # Look for the ending delimiter 136 | end = delimiter.indexIn(text, start + add) 137 | # Ending delimiter on this line? 138 | if end >= add: 139 | length = end - start + add + delimiter.matchedLength() 140 | self.setCurrentBlockState(0) 141 | # No; multi-line string 142 | else: 143 | self.setCurrentBlockState(in_state) 144 | length = len(text) - start + add 145 | # Apply formatting 146 | self.setFormat(start, length, style) 147 | # Look for the next match 148 | 149 | start = delimiter.indexIn(text, start + length) 150 | 151 | # Return True if still inside a multi-line string, False otherwise 152 | if self.currentBlockState() == in_state: 153 | return True 154 | else: 155 | return False 156 | 157 | 158 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/shortcuts.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | except: 5 | from PySide2.QtCore import * 6 | from PySide2.QtGui import * 7 | from PySide2.QtWidgets import * 8 | import shortcuts_UIs 9 | import os 10 | 11 | class shortcutsClass(QDialog, shortcuts_UIs.Ui_Dialog): 12 | def __init__(self, parent): 13 | super(shortcutsClass, self).__init__(parent) 14 | self.setupUi(self) 15 | self.table.horizontalHeader().setResizeMode(QHeaderView.Stretch) 16 | self.table.setColumnCount(2) 17 | self.table.setHorizontalHeaderLabels(['Action', 'Shortcut']) 18 | self.read() 19 | 20 | def read(self): 21 | src = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'shortcuts.txt') 22 | if os.path.exists(src): 23 | self.label.hide() 24 | lines = open(src).readlines() 25 | for i, l in enumerate(lines): 26 | self.table.insertRow(self.table.rowCount()) 27 | description, shortcut = l.split('>') 28 | item = QTableWidgetItem(description) 29 | self.table.setItem(i, 0, item) 30 | item.setFlags(Qt.ItemIsEnabled) 31 | item = QTableWidgetItem(shortcut) 32 | item.setFlags(Qt.ItemIsEnabled) 33 | self.table.setItem(i, 1, item) 34 | else: 35 | self.table.hide() 36 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/shortcuts.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 573 10 | 391 11 | 12 | 13 | 14 | Shortcuts list 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 12 25 | 75 26 | false 27 | true 28 | 29 | 30 | 31 | QFrame::NoFrame 32 | 33 | 34 | Shortcut list hot found!!! 35 | 36 | 37 | Qt::AutoText 38 | 39 | 40 | Qt::AlignCenter 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/shortcuts_UIs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'D:\Dropbox\Dropbox\pw_prefs\RnD\tools\pw_scriptEditor\multi_script_editor\widgets\shortcuts.ui' 4 | # 5 | # Created: Wed Apr 01 13:33:16 2015 6 | # by: pyside-uic 0.2.15 running on PySide 1.2.2 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | try: 11 | from PySide.QtCore import * 12 | from PySide.QtGui import * 13 | except: 14 | from PySide2.QtCore import * 15 | from PySide2.QtGui import * 16 | from PySide2.QtWidgets import * 17 | 18 | class Ui_Dialog(object): 19 | def setupUi(self, Dialog): 20 | Dialog.setObjectName("Dialog") 21 | Dialog.resize(573, 391) 22 | self.verticalLayout = QVBoxLayout(Dialog) 23 | self.verticalLayout.setObjectName("verticalLayout") 24 | self.table = QTableWidget(Dialog) 25 | self.table.setObjectName("table") 26 | self.table.setColumnCount(0) 27 | self.table.setRowCount(0) 28 | self.verticalLayout.addWidget(self.table) 29 | self.label = QLabel(Dialog) 30 | font = QFont() 31 | font.setPointSize(12) 32 | font.setWeight(75) 33 | font.setItalic(False) 34 | font.setBold(True) 35 | self.label.setFont(font) 36 | self.label.setFrameShape(QFrame.NoFrame) 37 | self.label.setTextFormat(Qt.AutoText) 38 | self.label.setAlignment(Qt.AlignCenter) 39 | self.label.setObjectName("label") 40 | self.verticalLayout.addWidget(self.label) 41 | 42 | self.retranslateUi(Dialog) 43 | QMetaObject.connectSlotsByName(Dialog) 44 | 45 | def retranslateUi(self, Dialog): 46 | Dialog.setWindowTitle(QApplication.translate("Dialog", "Shortcuts list", None)) 47 | self.label.setText(QApplication.translate("Dialog", "Shortcut list hot found!!!", None)) 48 | 49 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/tabWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | qt = 1 5 | except: 6 | from PySide2.QtCore import * 7 | from PySide2.QtGui import * 8 | from PySide2.QtWidgets import * 9 | qt = 2 10 | import os 11 | import numBarWidget, inputWidget 12 | reload(inputWidget) 13 | reload(numBarWidget) 14 | from managers import context 15 | 16 | 17 | style = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'style', 'completer.qss') 18 | if not os.path.exists(style): 19 | style=None 20 | 21 | 22 | class tabWidgetClass(QTabWidget): 23 | def __init__(self, parent=None): 24 | super(tabWidgetClass, self).__init__(parent) 25 | # ui 26 | self.setTabsClosable(True) 27 | self.setMovable(True) 28 | self.tabCloseRequested.connect(self.closeTab) 29 | self.tabBar().setContextMenuPolicy(Qt.CustomContextMenu) 30 | self.tabBar().customContextMenuRequested.connect(self.openMenu) 31 | newTabButton = QPushButton(self) 32 | newTabButton.setMaximumWidth(30) 33 | self.setCornerWidget(newTabButton, Qt.TopLeftCorner) 34 | newTabButton.setCursor(Qt.ArrowCursor) 35 | newTabButton.setText('+') 36 | newTabButton.clicked.connect(self.addNewTab) 37 | newTabButton.setToolTip("Add Tab") 38 | self.desk = QApplication.desktop() 39 | # variables 40 | self.p = parent 41 | self.lastSearch = [0, None] 42 | 43 | #connects 44 | self.currentChanged.connect(self.hideAllCompleters) 45 | 46 | def closeTab(self, i): 47 | if self.count() > 1: 48 | if self.getCurrentText(i).strip(): 49 | # if qt == 1: 50 | if self.yes_no_question('Close this tab without saving?\n'+self.tabText(i)): 51 | # if QMessageBox.question(self, 'Close Tab', 52 | # 'Close this tab without saving?\n'+self.tabText(i), 53 | # self.buttons) == QMessageBox.Yes: 54 | self.removeTab(i) 55 | # else: 56 | # if QMessageBox.question(self, 'Close Tab', 57 | # 'Close this tab without saving?\n'+self.tabText(i)) == QMessageBox.Yes: 58 | # self.removeTab(i) 59 | else: 60 | self.removeTab(i) 61 | 62 | def openMenu(self): 63 | menu = QMenu(self) 64 | menu.addAction(QAction('Rename Current Tab', self, triggered = self.renameTab)) 65 | menu.exec_(QCursor.pos()) 66 | 67 | def renameTab(self): 68 | index = self.currentIndex() 69 | text = self.tabText(index) 70 | result = QInputDialog.getText(self, 'New name', 'Enter New Name', text=text) 71 | if result[1]: 72 | self.setTabText(index, result[0]) 73 | 74 | def currentTabName(self): 75 | index = self.currentIndex() 76 | text = self.tabText(index) 77 | return text 78 | 79 | def addNewTab(self, name='New Tab', text = None): 80 | cont = container(text, self.p, self.desk)#, self.completer) 81 | cont.edit.saveSignal.connect(self.p.saveSession) 82 | # cont.edit.executeSignal.connect(self.p.executeSelected) 83 | self.addTab(cont, name) 84 | cont.edit.moveCursor(QTextCursor.Start) 85 | self.setCurrentIndex(self.count()-1) 86 | return cont.edit 87 | 88 | def getTabText(self, i): 89 | text = self.widget(i).edit.toPlainText() 90 | return text 91 | 92 | def addToCurrent(self, text): 93 | i = self.currentIndex() 94 | self.widget(i).edit.insertPlainText(text) 95 | 96 | def getCurrentSelectedText(self): 97 | i = self.currentIndex() 98 | text = self.widget(i).edit.getSelection() 99 | return text 100 | 101 | def getCurrentText(self, i=None): 102 | if i is None: 103 | i = self.currentIndex() 104 | text = self.widget(i).edit.toPlainText() 105 | return text 106 | 107 | def setCurrentText(self, text): 108 | i = self.currentIndex() 109 | self.widget(i).edit.setPlainText(text) 110 | 111 | 112 | def hideAllCompleters(self): 113 | for i in range(self.count()): 114 | self.widget(i).edit.completer.hideMe() 115 | 116 | def current(self): 117 | return self.widget(self.currentIndex()).edit 118 | 119 | ############################## editor commands 120 | def undo(self): 121 | self.current().undo() 122 | 123 | def redo(self): 124 | self.current().redo() 125 | 126 | def cut(self): 127 | self.current().cut() 128 | 129 | def copy(self): 130 | self.current().copy() 131 | 132 | def paste(self): 133 | self.current().paste() 134 | 135 | def search(self, text=None): 136 | if text: 137 | if text == self.lastSearch[0]: 138 | self.lastSearch[1] += 1 139 | else: 140 | self.lastSearch = [text, 0] 141 | self.lastSearch[1] = self.current().selectWord(text, self.lastSearch[1]) 142 | 143 | def replace(self, parts): 144 | find, rep = parts 145 | self.lastSearch = [find, 0] 146 | self.lastSearch[1] = self.current().selectWord(find, self.lastSearch[1], rep) 147 | self.current().selectWord(find, self.lastSearch[1]) 148 | 149 | def replaceAll(self, pat): 150 | find, rep = pat 151 | text = self.current().toPlainText() 152 | text = text.replace(find, rep) 153 | self.current().setPlainText(text) 154 | 155 | def comment(self): 156 | self.current().commentSelected() 157 | 158 | def yes_no_question(self, question): 159 | msg_box = QMessageBox(self) 160 | msg_box.setText(question) 161 | yes_button = msg_box.addButton("Yes", QMessageBox.YesRole) 162 | no_button = msg_box.addButton("No", QMessageBox.NoRole) 163 | msg_box.exec_() 164 | return msg_box.clickedButton() == yes_button 165 | 166 | 167 | class container(QWidget): 168 | def __init__(self, text, parent, desk): 169 | super(container, self).__init__() 170 | hbox = QHBoxLayout(self) 171 | hbox.setSpacing(0) 172 | hbox.setContentsMargins(0,0,0,0) 173 | # input widget 174 | self.edit = inputWidget.inputClass(parent, desk) 175 | self.edit.executeSignal.connect(parent.executeSelected) 176 | if text: 177 | self.edit.addText(text) 178 | # if not context == 'hou': 179 | # line number 180 | # if context == 'hou': 181 | # import hou 182 | # if hou.applicationVersion()[0] > 14: 183 | hbox.addWidget(self.edit) 184 | # return 185 | self.lineNum = numBarWidget.lineNumberBarClass(self.edit, self) 186 | self.edit.verticalScrollBar().valueChanged.connect(lambda :self.lineNum.update()) 187 | self.edit.inputSignal.connect(lambda :self.lineNum.update()) 188 | 189 | hbox.addWidget(self.lineNum) 190 | hbox.addWidget(self.edit) 191 | 192 | 193 | if __name__ == '__main__': 194 | app = QApplication([]) 195 | w = tabWidgetClass() 196 | w.show() 197 | app.exec_() -------------------------------------------------------------------------------- /multi_script_editor/widgets/themeEditor.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PySide.QtCore import * 3 | from PySide.QtGui import * 4 | qt = 1 5 | except: 6 | from PySide2.QtCore import * 7 | from PySide2.QtGui import * 8 | from PySide2.QtWidgets import * 9 | qt = 2 10 | import themeEditor_UIs as ui 11 | import settingsManager 12 | import os 13 | from .pythonSyntax import design 14 | from .pythonSyntax import syntaxHighLighter 15 | from . import inputWidget 16 | import icons_rcs 17 | 18 | 19 | class themeEditorClass(QDialog, ui.Ui_themeEditor): 20 | def __init__(self, parent = None, desk=None): 21 | super(themeEditorClass, self).__init__(parent) 22 | self.setupUi(self) 23 | self.preview_twd = inputWidget.inputClass(self, desk) 24 | self.preview_ly.addWidget(self.preview_twd) 25 | self.preview_twd.setPlainText(defaultText) 26 | self.splitter.setSizes([200,300]) 27 | self.s = settingsManager.scriptEditorClass() 28 | self.colors_lwd.itemDoubleClicked.connect(self.getNewColor) 29 | self.save_btn.clicked.connect(self.saveTheme) 30 | self.del_btn.clicked.connect(self.deleteTheme) 31 | self.themeList_cbb.currentIndexChanged.connect(self.updateColors) 32 | self.apply_btn.clicked.connect(self.apply) 33 | self.apply_btn.setText('Close') 34 | self.textSize_spb.valueChanged.connect(self.updateExample) 35 | 36 | self.fillUI() 37 | self.updateUI() 38 | self.updateColors() 39 | self.preview_twd.completer.updateCompleteList() 40 | self.namespace={} 41 | 42 | 43 | def fillUI(self, restore=None): 44 | if restore is None: 45 | restore = self.themeList_cbb.currentText() 46 | settings = self.s.readSettings() 47 | self.themeList_cbb.clear() 48 | self.themeList_cbb.addItem('default') 49 | if settings.get('colors'): 50 | for x in settings.get('colors'): 51 | self.themeList_cbb.addItem(x) 52 | if not restore: 53 | restore = settings.get('theme') 54 | if restore: 55 | index = self.themeList_cbb.findText(restore) 56 | self.themeList_cbb.setCurrentIndex(index) 57 | self.updateExample() 58 | 59 | 60 | def updateColors(self): 61 | curTheme = self.themeList_cbb.currentText() 62 | if curTheme == 'default': 63 | self.del_btn.setEnabled(0) 64 | colors = design.defaultColors 65 | else: 66 | self.del_btn.setEnabled(1) 67 | settings = self.s.readSettings() 68 | allThemes = settings.get('colors') 69 | if allThemes and curTheme in allThemes: 70 | colors = allThemes.get(curTheme) 71 | for k, v in design.getColors().items(): 72 | if not k in colors: 73 | colors[k] = v 74 | else: 75 | colors = design.getColors() 76 | 77 | self.colors_lwd.clear() 78 | for x in sorted(colors.keys()): 79 | if x == 'textsize': 80 | self.textSize_spb.setValue(colors['textsize']) 81 | else: 82 | item = QListWidgetItem(x) 83 | pix = QPixmap(QSize(16,16)) 84 | pix.fill(QColor(*colors[x])) 85 | item.setIcon(QIcon(pix)) 86 | item.setData(32, colors[x]) 87 | self.colors_lwd.addItem(item) 88 | self.updateExample() 89 | 90 | def updateExample(self): 91 | colors = self.getCurrentColors() 92 | self.preview_twd.applyPreviewStyle(colors) 93 | 94 | def getCurrentColors(self): 95 | colors = {} 96 | for i in range(self.colors_lwd.count()): 97 | item = self.colors_lwd.item(i) 98 | colors[item.text()] = item.data(32) 99 | colors['textsize'] = self.textSize_spb.value() 100 | return colors 101 | 102 | def getNewColor(self): 103 | items = self.colors_lwd.selectedItems() 104 | if items: 105 | item = items[0] 106 | init = QColor(*item.data(32)) 107 | color = QColorDialog.getColor(init ,self) 108 | if color.isValid(): 109 | newColor = (color.red(), color.green(), color.blue()) 110 | item.setData(32, newColor) 111 | pix = QPixmap(QSize(16,16)) 112 | pix.fill(QColor(*newColor)) 113 | item.setIcon(QIcon(pix)) 114 | self.updateExample() 115 | 116 | def saveTheme(self): 117 | text = self.themeList_cbb.currentText() or 'NewTheme' 118 | name = QInputDialog.getText(self, 'Theme name', 'Enter Theme name', QLineEdit.Normal, text) 119 | if name[1]: 120 | name = name[0] 121 | if name == 'default': 122 | name = 'Not default' 123 | settings = self.s.readSettings() 124 | if 'colors' in settings: 125 | if name in settings['colors']: 126 | if not self.yes_no_question('Replace exists?'): 127 | return 128 | 129 | colors = self.getCurrentColors() 130 | if 'colors' in settings: 131 | settings['colors'][name] = colors 132 | else: 133 | settings['colors'] = {name: colors} 134 | self.s.writeSettings(settings) 135 | self.fillUI(name) 136 | # self.updateUI() 137 | 138 | def deleteTheme(self): 139 | text = self.themeList_cbb.currentText() 140 | if text: 141 | if self.yes_no_question('Remove current theme?'): 142 | name = self.themeList_cbb.currentText() 143 | settings = self.s.readSettings() 144 | if 'colors' in settings: 145 | if name in settings['colors']: 146 | del settings['colors'][name] 147 | self.s.writeSettings(settings) 148 | self.fillUI(False) 149 | self.updateUI() 150 | 151 | def updateUI(self): 152 | if not self.themeList_cbb.count(): 153 | self.apply_btn.setEnabled(0) 154 | else: 155 | self.apply_btn.setEnabled(1) 156 | 157 | def apply(self): 158 | name = self.themeList_cbb.currentText() 159 | if name: 160 | settings = self.s.readSettings() 161 | settings['theme'] = name 162 | self.s.writeSettings(settings) 163 | self.accept() 164 | 165 | def keyPressEvent(self, event): 166 | if event.key() == Qt.Key_Escape: 167 | event.ignore() 168 | else: 169 | super(themeEditorClass, self).keyPressEvent(event) 170 | 171 | def current(self): 172 | pass 173 | # print self.colors_lwd.selectedItems()[0].data(32) 174 | 175 | def yes_no_question(self, question): 176 | msg_box = QMessageBox(self) 177 | msg_box.setText(question) 178 | yes_button = msg_box.addButton("Yes", QMessageBox.YesRole) 179 | no_button = msg_box.addButton("No", QMessageBox.NoRole) 180 | msg_box.exec_() 181 | return msg_box.clickedButton() == yes_button 182 | 183 | defaultText = r'''@decorator(param=1) 184 | def f(x): 185 | """ Syntax Highlighting Demo 186 | @param x Parameter""" 187 | s = ("Test", 2+3, {'a': 'b'}, x) # Comment 188 | print s[0].lower() 189 | 190 | class Foo: 191 | def __init__(self): 192 | string = 'newline' 193 | self.makeSense(whatever=1) 194 | 195 | def makeSense(self, whatever): 196 | self.sense = whatever 197 | 198 | x = len('abc') 199 | print(f.__doc__) 200 | ''' 201 | 202 | 203 | 204 | 205 | if __name__ == '__main__': 206 | app = QApplication([]) 207 | w = themeEditorClass() 208 | w.show() 209 | qss = os.path.join(os.path.dirname(os.path.dirname(__file__)),'style', 'style.css') 210 | if os.path.exists(qss): 211 | w.setStyleSheet(open(qss).read()) 212 | app.exec_() 213 | 214 | 215 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/themeEditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | themeEditor 4 | 5 | 6 | 7 | 0 8 | 0 9 | 724 10 | 461 11 | 12 | 13 | 14 | Code Theme Editor 15 | 16 | 17 | 18 | 19 | 20 | Qt::Horizontal 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Completer text size 33 | 34 | 35 | 36 | 37 | 38 | 39 | 9 40 | 41 | 42 | 25 43 | 44 | 45 | 11 46 | 47 | 48 | 49 | 50 | 51 | 52 | Qt::Horizontal 53 | 54 | 55 | 56 | 40 57 | 20 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 60 78 | 16777215 79 | 80 | 81 | 82 | Save 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 60 91 | 16777215 92 | 93 | 94 | 95 | Del 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | Qt::Horizontal 114 | 115 | 116 | 117 | 40 118 | 20 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | Save 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/themeEditor_UI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'D:\Dropbox\Dropbox\pw_prefs\RnD\tools\pw_scriptEditor\widgets\themeEditor.ui' 4 | # 5 | # Created: Mon Mar 16 10:29:57 2015 6 | # by: PyQt4 UI code generator 4.11.3 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | from PyQt4 import QtCore, QtGui 11 | 12 | try: 13 | _fromUtf8 = QtCore.QString.fromUtf8 14 | except AttributeError: 15 | def _fromUtf8(s): 16 | return s 17 | 18 | try: 19 | _encoding = QtGui.QApplication.UnicodeUTF8 20 | def _translate(context, text, disambig): 21 | return QtGui.QApplication.translate(context, text, disambig, _encoding) 22 | except AttributeError: 23 | def _translate(context, text, disambig): 24 | return QtGui.QApplication.translate(context, text, disambig) 25 | 26 | class Ui_themeEditor(object): 27 | def setupUi(self, themeEditor): 28 | themeEditor.setObjectName(_fromUtf8("themeEditor")) 29 | themeEditor.resize(724, 461) 30 | self.verticalLayout_3 = QtGui.QVBoxLayout(themeEditor) 31 | self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) 32 | self.splitter = QtGui.QSplitter(themeEditor) 33 | self.splitter.setOrientation(QtCore.Qt.Horizontal) 34 | self.splitter.setObjectName(_fromUtf8("splitter")) 35 | self.widget = QtGui.QWidget(self.splitter) 36 | self.widget.setObjectName(_fromUtf8("widget")) 37 | self.verticalLayout_2 = QtGui.QVBoxLayout(self.widget) 38 | self.verticalLayout_2.setMargin(0) 39 | self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) 40 | self.colors_lwd = QtGui.QListWidget(self.widget) 41 | self.colors_lwd.setObjectName(_fromUtf8("colors_lwd")) 42 | self.verticalLayout_2.addWidget(self.colors_lwd) 43 | self.horizontalLayout_3 = QtGui.QHBoxLayout() 44 | self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3")) 45 | self.label = QtGui.QLabel(self.widget) 46 | self.label.setObjectName(_fromUtf8("label")) 47 | self.horizontalLayout_3.addWidget(self.label) 48 | self.textSize_spb = QtGui.QSpinBox(self.widget) 49 | self.textSize_spb.setMinimum(9) 50 | self.textSize_spb.setMaximum(25) 51 | self.textSize_spb.setProperty("value", 11) 52 | self.textSize_spb.setObjectName(_fromUtf8("textSize_spb")) 53 | self.horizontalLayout_3.addWidget(self.textSize_spb) 54 | spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 55 | self.horizontalLayout_3.addItem(spacerItem) 56 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 57 | self.layoutWidget = QtGui.QWidget(self.splitter) 58 | self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) 59 | self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) 60 | self.verticalLayout.setMargin(0) 61 | self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) 62 | self.horizontalLayout = QtGui.QHBoxLayout() 63 | self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) 64 | self.themeList_cbb = QtGui.QComboBox(self.layoutWidget) 65 | self.themeList_cbb.setObjectName(_fromUtf8("themeList_cbb")) 66 | self.horizontalLayout.addWidget(self.themeList_cbb) 67 | self.save_btn = QtGui.QPushButton(self.layoutWidget) 68 | self.save_btn.setMaximumSize(QtCore.QSize(60, 16777215)) 69 | self.save_btn.setObjectName(_fromUtf8("save_btn")) 70 | self.horizontalLayout.addWidget(self.save_btn) 71 | self.del_btn = QtGui.QPushButton(self.layoutWidget) 72 | self.del_btn.setMaximumSize(QtCore.QSize(60, 16777215)) 73 | self.del_btn.setObjectName(_fromUtf8("del_btn")) 74 | self.horizontalLayout.addWidget(self.del_btn) 75 | self.horizontalLayout.setStretch(0, 1) 76 | self.verticalLayout.addLayout(self.horizontalLayout) 77 | self.preview_ly = QtGui.QVBoxLayout() 78 | self.preview_ly.setObjectName(_fromUtf8("preview_ly")) 79 | self.verticalLayout.addLayout(self.preview_ly) 80 | self.verticalLayout.setStretch(1, 1) 81 | self.verticalLayout_3.addWidget(self.splitter) 82 | self.horizontalLayout_2 = QtGui.QHBoxLayout() 83 | self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) 84 | spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 85 | self.horizontalLayout_2.addItem(spacerItem1) 86 | self.apply_btn = QtGui.QPushButton(themeEditor) 87 | self.apply_btn.setObjectName(_fromUtf8("apply_btn")) 88 | self.horizontalLayout_2.addWidget(self.apply_btn) 89 | self.verticalLayout_3.addLayout(self.horizontalLayout_2) 90 | self.verticalLayout_3.setStretch(0, 1) 91 | 92 | self.retranslateUi(themeEditor) 93 | QtCore.QMetaObject.connectSlotsByName(themeEditor) 94 | 95 | def retranslateUi(self, themeEditor): 96 | themeEditor.setWindowTitle(_translate("themeEditor", "Code Theme Editor", None)) 97 | self.label.setText(_translate("themeEditor", "Completer text size", None)) 98 | self.save_btn.setText(_translate("themeEditor", "Save", None)) 99 | self.del_btn.setText(_translate("themeEditor", "Del", None)) 100 | self.apply_btn.setText(_translate("themeEditor", "Save", None)) 101 | 102 | -------------------------------------------------------------------------------- /multi_script_editor/widgets/themeEditor_UIs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'D:\Dropbox\Dropbox\pw_prefs\RnD\tools\pw_scriptEditor\widgets\themeEditor.ui' 4 | # 5 | # Created: Mon Mar 16 10:29:58 2015 6 | # by: pyside-uic 0.2.15 running on PySide 1.2.2 7 | # 8 | # WARNING! All changes made in this file will be lost! 9 | 10 | try: 11 | from PySide.QtCore import * 12 | from PySide.QtGui import * 13 | except: 14 | from PySide2.QtCore import * 15 | from PySide2.QtGui import * 16 | from PySide2.QtWidgets import * 17 | 18 | class Ui_themeEditor(object): 19 | def setupUi(self, themeEditor): 20 | themeEditor.setObjectName("themeEditor") 21 | themeEditor.resize(724, 461) 22 | self.verticalLayout_3 = QVBoxLayout(themeEditor) 23 | self.verticalLayout_3.setObjectName("verticalLayout_3") 24 | self.splitter = QSplitter(themeEditor) 25 | self.splitter.setOrientation(Qt.Horizontal) 26 | self.splitter.setObjectName("splitter") 27 | self.widget = QWidget(self.splitter) 28 | self.widget.setObjectName("widget") 29 | self.verticalLayout_2 = QVBoxLayout(self.widget) 30 | self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) 31 | self.verticalLayout_2.setObjectName("verticalLayout_2") 32 | self.colors_lwd = QListWidget(self.widget) 33 | self.colors_lwd.setObjectName("colors_lwd") 34 | self.verticalLayout_2.addWidget(self.colors_lwd) 35 | self.horizontalLayout_3 = QHBoxLayout() 36 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 37 | self.label = QLabel(self.widget) 38 | self.label.setObjectName("label") 39 | self.horizontalLayout_3.addWidget(self.label) 40 | self.textSize_spb = QSpinBox(self.widget) 41 | self.textSize_spb.setMinimum(9) 42 | self.textSize_spb.setMaximum(25) 43 | self.textSize_spb.setProperty("value", 11) 44 | self.textSize_spb.setObjectName("textSize_spb") 45 | self.horizontalLayout_3.addWidget(self.textSize_spb) 46 | spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 47 | self.horizontalLayout_3.addItem(spacerItem) 48 | self.verticalLayout_2.addLayout(self.horizontalLayout_3) 49 | self.layoutWidget = QWidget(self.splitter) 50 | self.layoutWidget.setObjectName("layoutWidget") 51 | self.verticalLayout = QVBoxLayout(self.layoutWidget) 52 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 53 | self.verticalLayout.setObjectName("verticalLayout") 54 | self.horizontalLayout = QHBoxLayout() 55 | self.horizontalLayout.setObjectName("horizontalLayout") 56 | self.themeList_cbb = QComboBox(self.layoutWidget) 57 | self.themeList_cbb.setObjectName("themeList_cbb") 58 | self.horizontalLayout.addWidget(self.themeList_cbb) 59 | self.save_btn = QPushButton(self.layoutWidget) 60 | self.save_btn.setMaximumSize(QSize(60, 16777215)) 61 | self.save_btn.setObjectName("save_btn") 62 | self.horizontalLayout.addWidget(self.save_btn) 63 | self.del_btn = QPushButton(self.layoutWidget) 64 | self.del_btn.setMaximumSize(QSize(60, 16777215)) 65 | self.del_btn.setObjectName("del_btn") 66 | self.horizontalLayout.addWidget(self.del_btn) 67 | self.horizontalLayout.setStretch(0, 1) 68 | self.verticalLayout.addLayout(self.horizontalLayout) 69 | self.preview_ly = QVBoxLayout() 70 | self.preview_ly.setObjectName("preview_ly") 71 | self.verticalLayout.addLayout(self.preview_ly) 72 | self.verticalLayout.setStretch(1, 1) 73 | self.verticalLayout_3.addWidget(self.splitter) 74 | self.horizontalLayout_2 = QHBoxLayout() 75 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 76 | spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 77 | self.horizontalLayout_2.addItem(spacerItem1) 78 | self.apply_btn = QPushButton(themeEditor) 79 | self.apply_btn.setObjectName("apply_btn") 80 | self.horizontalLayout_2.addWidget(self.apply_btn) 81 | self.verticalLayout_3.addLayout(self.horizontalLayout_2) 82 | self.verticalLayout_3.setStretch(0, 1) 83 | 84 | self.retranslateUi(themeEditor) 85 | QMetaObject.connectSlotsByName(themeEditor) 86 | 87 | def retranslateUi(self, themeEditor): 88 | themeEditor.setWindowTitle(QApplication.translate("themeEditor", "Code Theme Editor", None)) 89 | self.label.setText(QApplication.translate("themeEditor", "Completer text size", None)) 90 | self.save_btn.setText(QApplication.translate("themeEditor", "Save", None)) 91 | self.del_btn.setText(QApplication.translate("themeEditor", "Del", None)) 92 | self.apply_btn.setText(QApplication.translate("themeEditor", "Save", None)) 93 | 94 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Multi Script Editor v2.0.4 2 | 3 | ![alt tag](http://www.paulwinex.ru/wp-content/uploads/2015/04/mse_banner.jpg) 4 | 5 | #### [paulwinex.com](http://paulwinex.com/portfolio/multi-script-editor/) 6 | 7 | ## [Tutorials](https://vimeo.com/channels/multiscripteditor) 8 | 9 | This is a cross application, cross platform and open source Python editor, which can be run as a standalone application 10 | or embedded in another application. The main purpose for integration - the ability to script in Python. 11 | 12 | ### Key features 13 | 14 | - Preserve and load of tabs and code in them 15 | - Interactive execute of the selected code by pressing Ctrl + Enter 16 | - Adjust the color theme of the code editor 17 | - Code completion (module [jedi](https://github.com/davidhalter/jedi)) 18 | - Context completion for different functions like existing nodes and path in scene 19 | 20 | ### Existing integration modules 21 | 22 | - Houdini 13-17 23 | - Nuke 8-10 24 | - Maya 2014-2017 25 | - 3DsMax 2014-2017 26 | 27 | RV Integration in [nebukadhezer branch](https://github.com/nebukadhezer/multi_script_editor) 28 | 29 | If necessary, you can extend this to make your own integration module. 30 | The main pre condition - Should be used Python2.7. 31 | 32 | 33 | ### Houdini features 34 | - Code completion for all modules and return types (remastered hou library) 35 | - Context completion for functions CreateNode, CreateInputNode and CreateOutputNode with existing houdini node types 36 | - Context completion string for absolute houdini internal path and node parameters. To use this complete start string with "/ or '/ 37 | - Drag&Drop Houdini nodes and parameters fills in their path. Use Alt modifier to wrap node or parameter as code like in Houdini Python Shell. 38 | - Reading and writing to PythonSOP code and asset sections 39 | 40 | ### Nuke features 41 | - Code completion for all modules and return types (remastered nuke library) 42 | - Context completion for createNode with existing Nuke node types 43 | - Context completion for function toNode with existing nodes in current script 44 | - Converting selected nodes to code with function nuke.toNode 45 | - Searching and converting nodes to code from clipboard 46 | - Reading and writing from PythonKnobs code 47 | 48 | ### Maya features 49 | - Save code to shelf and accept dropped shelf button code, like default Maya script editor 50 | - Drag&Drop Maya nodes fills in their names. Use Alt modifier to wrap node as code. Import PyMEL before doing this! 51 | - Context completion for function PyNode with existing nodes in current scene 52 | - Context completion for function pm.createNode and cmds.createNode with existing Maya node types 53 | 54 | ### 3DsMax features 55 | - Works for now... 56 | 57 | 58 | # How to install 59 | 60 | 61 | [Standalone](https://github.com/nebukadhezer/multi_script_editor) 62 | [Houdini install](https://github.com/nebukadhezer/multi_script_editor) 63 | [Maya install](https://github.com/nebukadhezer/multi_script_editor) 64 | [Nuke install](https://github.com/nebukadhezer/multi_script_editor) 65 | [3DsMax install](https://github.com/nebukadhezer/multi_script_editor) 66 | 67 | You can use single module installation for each case. Just extract module `multi_script_editor` 68 | to somewhere (no spaces and non ascii characters in path) and add this path to PYTHONPATH before start your app. 69 | 70 | For example: 71 | 72 | Extract to `'/home/username/myscripts/multi_script_editor'` 73 | 74 | Add this path to PYTHONPATH environment variable 75 | 76 | Windows: 77 | 78 | `set PYTHONPATH=c:/users/username/myscripts` 79 | 80 | Linux 81 | 82 | `export PYTHONPATH=/home/username/myscripts` 83 | 84 | Python 85 | 86 | `os.environ['PYTHONPATH'] = '/home/username/myscripts'` 87 | 88 | Do this for each app. 89 | 90 | # License 91 | 92 | This project is licensed under the MIT License 93 | -------------------------------------------------------------------------------- /readme_3dsmax.md: -------------------------------------------------------------------------------- 1 | ## 3DsMax Install 2 | - extract module `multi_script_editor` to PYTHONPATH 3 | 4 | Example: `{MAX_INSTALL_DIR}/python/multi_script_editor` 5 | - create a menu, toolbar, etc with the following maxscript: 6 | 7 | ```maxscript 8 | python.executefile("path\\to\\multi_script_editor\\managers\\run_3dsmax.py") 9 | ``` 10 | or 11 | ``` 12 | macroScript MultiScriptEditor 13 | category:"scripts" 14 | toolTip:"MultiScriptEditor" 15 | ( 16 | python.Execute "import multi_script_editor" 17 | python.Execute "multi_script_editor.show3DSMax()" 18 | ) 19 | 20 | ``` -------------------------------------------------------------------------------- /readme_houdini.md: -------------------------------------------------------------------------------- 1 | ## Houdini install 2 | 3 | ### Houdini 13 4 | 5 | - install PySide to default Python interpreter 6 | - create new tool on shelf 7 | - extract module `multi_script_editor` to PYTHONPATH 8 | 9 | Example: `{HOME}/Documents/Houdini13.X/scripts/python/multi_script_editor` 10 | 11 | ```python 12 | import multi_script_editor 13 | multi_script_editor.showHoudini(ontop=1) 14 | ``` 15 | 16 | ### Houdini 14+ 17 | 18 | - extract module `multi_script_editor` to PYTHONPATH 19 | 20 | Example: `{HOME}/Documents/Houdini13.X/scripts/python/multi_script_editor` 21 | - create new tool on shelf 22 | 23 | ```python 24 | import multi_script_editor 25 | multi_script_editor.showHoudini(name='Multi Script Editor', replacePyPanel=1, hideTitleMenu=0) 26 | ``` 27 | 28 | Also you can use .pypanel file without hqt module 29 | >/managers/houdini/multi_script_editor_16.pypanel (for Houdini 14-16) 30 | >/managers/houdini/multi_script_editor_17.pypanel (for Houdini 17+) 31 | -------------------------------------------------------------------------------- /readme_maya.md: -------------------------------------------------------------------------------- 1 | ## Maya Install 2 | 3 | - Create shelf button with code 4 | - extract module `multi_script_editor` to PYTHONPATH 5 | 6 | Example: `{HOME}/Documents/maya/scripts/multi_script_editor` 7 | 8 | 9 | ```python 10 | import multi_script_editor 11 | multi_script_editor.showMaya(dock=True) 12 | ``` 13 | -------------------------------------------------------------------------------- /readme_nuke.md: -------------------------------------------------------------------------------- 1 | ## Nuke Install 2 | 3 | - extract module `multi_script_editor` to PYTHONPATH 4 | 5 | Example: `{HOME}/.nuke/multi_script_editor` 6 | - Add next code to menu.py: 7 | 8 | ```python 9 | 10 | import multi_script_editor 11 | 12 | # add to menu 13 | menubar = nuke.menu("Nuke") 14 | toolMenu = menubar.addMenu('&Tools') 15 | toolMenu.addCommand("Multi Script Editor", multi_script_editor.showNuke) 16 | 17 | # create new pane 18 | multi_script_editor.showNuke(panel=True) 19 | ``` 20 | -------------------------------------------------------------------------------- /readme_standalone.md: -------------------------------------------------------------------------------- 1 | 2 | ### Standalone 3 | 4 | #### How to start 5 | 6 | - install Python 2.7 7 | - install PySide 8 | - use run.cmd (Windows) or run.sh (Linux) to start 9 | -------------------------------------------------------------------------------- /releaseNote.md: -------------------------------------------------------------------------------- 1 | ## v2.1 2 | ### What new 3 | 4 | - update all managers 5 | - update auto completion fou Houdini ? 6 | 7 | 8 | ## v2.0.5 9 | ### Fix 10 | - Support Houdini 16.5 11 | - update Houdini auto completion 12 | - fix connector for Maya 2017 13 | - add hqt.py by default 14 | 15 | ## v2.0.4 16 | ### Fix 17 | - comment/uncomment function 18 | - optimised Nuke library 19 | - fixed line number bar for Houdini 20 | - update 3DsMax connector script 21 | 22 | ## v2.0.3 23 | ### What new 24 | 25 | #### Editor 26 | 27 | - added default text editors functions in Edit menu: undo, redo, copy, paste etc 28 | - added Find and Replace window 29 | - auto indent on new line and new block after ":" 30 | - backspace remove 4 spaces 31 | - duplicate line or selected text by Ctrl+D 32 | - Alt+Q to comment/uncomment line or selected lines 33 | 34 | #### Houdini 35 | 36 | - added app-context menu for Houdini 37 | - read python scripts from node sections or PythonSOP code 38 | - save python script to section or PythonSOP node 39 | - added Houdini path autocomplete. Path contains the absolute path to the Houdini node and its parameters 40 | - finished default Houdini autocomplete modules. Now all functions return correct types 41 | - added automatic wrap dragged nodes and parameters with ALT modifier (H14) 42 | - no need to import main module (hou) to make completer worked 43 | 44 | #### Nuke 45 | 46 | - added app-context menu for Nuke 47 | - read script from PythonButtonKnob 48 | - save script to PythonButtonKnob 49 | - get selected nodes as code "nuke.toNode('')" 50 | - search nodes in clipboard text 51 | - added autocomplete for "toNode" function with existing nodes 52 | - added autocomplete for "allNodes" function with all node types (parameter "filter=") 53 | - finished default Nuke autocomplete modules. Now all functions return correct types 54 | - no need to import main module (import nuke) to make completer worked 55 | 56 | #### Maya 57 | 58 | - added dock control mode for Maya 59 | - added context autocomplete for Maya function "PyNode" with existing nodes 60 | - added automatic wrap dragged nodes for Maya with ALT modifier 61 | 62 | ### Fix 63 | - some corrections output window 64 | - fast complete first line with Tab key or Enter key 65 | - fix menu stylesheet for stand alone 66 | - fixed line numbers widget for Houdini 67 | - now comments and doc strings have different colors in theme 68 | - editor format set to UTF8 69 | 70 | -------------------------------------------- 71 | 72 | ## v2.0.1 73 | ### What new 74 | - added context depended completer for 'createNode' function 75 | - Hodudini : createNode, createInputNode and createOutputNode 76 | - Maya: pm.createNode and cmds.createNode 77 | - Nuke: nuke.createNode and nuke.nodes 78 | 79 | ### Fix 80 | - move complete when widget geometry offscreen 81 | - Nuke 8 now worked 82 | - Maya 2015 now worked 83 | 84 | -------------------------------------------- 85 | 86 | ## v2.0 87 | ### What new 88 | - added auto complete 89 | - fix auto completion for Houdini 90 | - fix auto completion for Nuke 91 | 92 | ## v1.0 93 | 94 | Not released -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='multi_script_editor', 4 | version='2.1', 5 | description='Python editor for multiple platforms', 6 | long_description='Python editor for multiple platforms and CG software applications', 7 | classifiers=[ 8 | 'Development Status :: Release 2.1', 9 | 'License :: OSI Approved :: MIT License', 10 | 'Programming Language :: Python :: 2.7', 11 | ], 12 | keywords='python ide script_editor', 13 | url='https://github.com/paulwinex/pw_MultiScriptEditor', 14 | author='Paul Winex', 15 | author_email='paulwinex@gmail.com', 16 | license='MIT', 17 | packages=find_packages(), 18 | install_requires=[], 19 | include_package_data=True, 20 | zip_safe=False) 21 | --------------------------------------------------------------------------------