Skip to content
Snippets Groups Projects
text_intellisense.py 12.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ***** BEGIN GPL LICENSE BLOCK *****
    #
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License
    # as published by the Free Software Foundation; either version 2
    # of the License, or (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program; if not, write to the Free Software Foundation,
    # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ***** END GPL LICENCE BLOCK *****
    
    bl_info = {
    
    Campbell Barton's avatar
    Campbell Barton committed
        "name": "Intellisense for Text Editor",
        "author": "Mackraken",
    
        "version": (0, 2, 1),
        "blender": (2, 78, 5),
    
    Campbell Barton's avatar
    Campbell Barton committed
        "location": "Ctrl + Space at Text Editor",
        "description": "Adds intellense to the Text Editor",
        "warning": "Only works with 2.57 intellisense",
        "wiki_url": "",
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    
    Campbell Barton's avatar
    Campbell Barton committed
        "category": "Development"}
    
    from bpy_extras import keyconfig_utils
    
    
    
    def complete(context):
    
        from console import intellisense
        from console_python import get_console
    
        sc = context.space_data
        text = sc.text
    
        region = context.region
        for area in context.screen.areas:
            if area.type == "CONSOLE":
                region = area.regions[1]
                break
    
        console = get_console(hash(region))[0]
    
        line = text.current_line.body
        cursor = text.current_character
    
        result = intellisense.expand(line, cursor, console.locals, bpy.app.debug)
    
    class Intellimenu(bpy.types.Menu):
    
        bl_label = ""
        bl_idname = "IntelliMenu"
    
        def draw(self, context):
            layout = self.layout
            # Very ugly see how can i fix this
            options = complete(context)
    
            options = options[2].split("  ")
            for op in options:
                layout.operator("text.intellioptions", text=op).text = op
    
    # This operator executes when hits Ctrl+Space at the text editor
    
    class Intellisense(bpy.types.Operator):
        bl_idname = "text.intellisense"
        bl_label = "Text Editor Intellisense"
    
        """
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
        """
    
        def execute(self, context):
            sc = context.space_data
            text = sc.text
    
            if text.current_character > 0:
                result = complete(context)
    
                if result[2] == "":
                    text.current_line.body = result[0]
                    bpy.ops.text.move(type='LINE_END')
                else:
                    bpy.ops.wm.call_menu(name=Intellimenu.bl_idname)
    
    # this operator completes the line with the options you choose from the menu
    class Intellioptions(bpy.types.Operator):
        bl_idname = "text.intellioptions"
        bl_label = "Intellisense options"
    
        text: bpy.props.StringProperty()
    
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
    
        def execute(self, context):
            sc = context.space_data
            text = sc.text
    
            if not text:
                self.report({'WARNING'},
                            "No Text-Block active. Operation Cancelled")
    
            comp = self.text
            line = text.current_line.body
    
            lline = len(line)
            lcomp = len(comp)
    
            # intersect text
            intersect = [-1, -1]
    
            for i in range(lcomp):
                val1 = comp[0: i + 1]
    
                for j in range(lline):
                    val2 = line[lline - j - 1::]
                    # print("    ",j, val2)
    
                    if val1 == val2:
                        intersect = [i, j]
                        break
    
            if intersect[0] > -1:
                newline = line[0: lline - intersect[1] - 1] + comp
            else:
                newline = line + comp
    
            # print(newline)
            text.current_line.body = newline
    
            bpy.ops.text.move(type='LINE_END')
    
    def send_console(context, alls=0):
    
        sc = context.space_data
        text = sc.text
    
        for area in bpy.context.screen.areas:
            if area.type == "CONSOLE":
                from console_python import get_console
    
                console = get_console(hash(area.regions[1]))[0]
    
        if console is None:
            return {'FINISHED'}
    
        if alls:
            for l in text.lines:
                console.push(l.body)
        else:
            # print(console.prompt)
            console.push(text.current_line.body)
    
    class TestLine(bpy.types.Operator):
    
        bl_idname = "text.test_line"
        bl_label = "Test line"
    
        all: bpy.props.BoolProperty(default=False)
    
        """
        @classmethod
        def poll(cls, context):
            return context.active_object is not None
        """
        def execute(self, context):
            # print("test line")
    
            # send_console(context, self.all)
            sc = context.space_data
            text = sc.text
    
            if not text:
                self.report({'WARNING'},
                            "No Text-Block active. Operation Cancelled")
    
            line = text.current_line.body
            console = None
    
            for area in bpy.context.screen.areas:
                if area.type == "CONSOLE":
                    from console_python import get_console
    
                    console = get_console(hash(area.regions[1]))[0]
    
            if console is None:
                return {'FINISHED'}
    
            forindex = line.find("for ")
            if forindex > -1:
    
                var = line[forindex + 4: -1]
                var = var[0: var.find(" ")]
                state = line[line.rindex(" ") + 1: -1]
    
                command = var + " = " + state + "[0]"
    
            # print(command)
            try:
                console.push(command)
            except:
                pass
    
            bpy.ops.text.line_break()
    
    
    class SetBreakPoint(bpy.types.Operator):
    
        bl_idname = "text.set_breakpoint"
        bl_label = "Set Breakpoint"
    
        def execute(self, context):
    
            sc = bpy.context.space_data
            text = sc.text
    
            if not text:
                self.report({'WARNING'},
                            "No Text-Block active. Operation Cancelled")
    
            line = text.current_line
            br = " #breakpoint"
            # print(line.body.find(br))
            if line.body.find(br) > -1:
    
                line.body = line.body.replace(br, "")
            else:
    
    class Debug(bpy.types.Operator):
        bl_idname = "text.debug"
        bl_label = "Debug"
    
        def execute(self, context):
            success = False
    
            binpath = bpy.app.binary_path
    
            addonspath = binpath[
                    0: binpath.rindex("\\") + 1] + \
                    str(bpy.app.version[0]) + "." + \
                    str(bpy.app.version[1]) + "\\scripts\\addons\\"
    
            sc = context.space_data
            text = sc.text
    
            if not text:
                self.report({'WARNING'},
                            "No Text-Block active. Operation Cancelled")
    
            filepath = addonspath + "debug.py"
            with open(filepath, "w") as files:
                files.write("import pdb\n")
    
                for line in text.lines:
                    l = line.body
    
                    if line.body.find(br) > -1:
                        indent = ""
                        for letter in line.body:
    
                            if not letter.isalpha():
                                indent += letter
                            else:
                                break
                        files.write(l[0: -len(br)] + "\n")
    
                        files.write(indent + "pdb.set_trace()\n")
    
                    else:
                        files.write(line.body + "\n")
    
            # not working as runcall expects a specific function to be called not a string
            # import pdb
            # import debug
            # pdb.runcall("debug")
            if success:
                self.report({'INFO'},
                            "Created a debug file: {}".format(filepath))
    
    class DebugPanel(bpy.types.Panel):
        bl_label = "Debug"
        bl_space_type = "TEXT_EDITOR"
        bl_region_type = "UI"
        # bl_context = "object"
    
    
        text: bpy.props.StringProperty()
    
    
        def draw(self, context):
            layout = self.layout
            row = layout.row()
    
            row = layout.row()
            row.operator("text.debug", text="Debug")
            row = layout.row()
            row.operator("text.set_breakpoint")
            row = layout.row()
            row.operator("text.test_line").all = False
            row = layout.row()
            row.operator("text.test_line", text="Test All").all = True
    
            row = layout.row()
            row.label(text="Coming Soon ...")
    
    
    # ASSIGN A KEY
    
    # section = Input section. "Window, Text, ..."
    # name = operator name or wm.call_menu
    # type = key
    # event = keyboard event (Press, release, ...)
    # mods = array containing key modifiers (["ctrl", "alt", "shift"]
    # propvalue = menu name, if name is set to "wm.call_menu"
    # overwrite doesnt work at the moment
    
    def assignKey(section, name, type, event, mods=[], propvalue="", overwrite=0):
    
        kconf = bpy.context.window_manager.keyconfigs.active
    
        # check section
        validsections = [item.name for item in kconf.keymaps]
        if section not in validsections:
            print(section + " is not a valid section.")
            # print(validsections)
            return False
    
        # check type
        type = type.upper()
        validkeys = [item.identifier for item in bpy.types.KeyMapItem.bl_rna.properties['type'].enum_items]
    
        if type not in validkeys:
            print(type + " is not a valid key.")
            # print(validkeys)
            return False
    
        # check event
        event = event.upper()
        validevents = [item.identifier for item in bpy.types.KeyMapItem.bl_rna.properties['value'].enum_items]
    
        if event not in validevents:
            print(event + " is not a valid event.")
            # print(validevents)
    
        kmap = kconf.keymaps[section]
    
        # get mods
        for i, mod in enumerate(mods):
            mods[i] = mod.lower()
    
        # any, shift, ctrl, alt, oskey
        kmod = [False, False, False, False, False]
    
        if "any" in mods:
            kmod[0] = True
        if "shift" in mods:
            kmod[1] = True
        if "ctrl" in mods:
            kmod[2] = True
        if "alt" in mods:
            kmod[3] = True
        if "oskey" in mods:
            kmod[4] = True
    
        # check if key exist
        kexists = False
    
        for key in kmap.keymap_items:
            keymods = [key.any, key.shift, key.ctrl, key.alt, key.oskey]
            if key.type == type and keymods == kmod:
                kexists = True
                print(key, "key exists")
                break
    
        if kexists:
            # overwrite?
            if overwrite:
                key.idname = name
                # key.type = type
    
        else:
            # create key
            key = kmap.keymap_items.new(name, type, event, False)
            key.any = kmod[0]
            key.shift = kmod[1]
            key.ctrl = kmod[2]
            key.alt = kmod[3]
            key.oskey = kmod[4]
    
            if propvalue != "":
                key.properties.name = propvalue
    
    
    # ------------------- REGISTER ------------------------------------------------
    
    # For explanation of the data structure look in modules\bpy_extras\keyconfig_utils
    # (1) Identifier, (2) operator call/key assigment, (3) properties passed on call
    KEYMAPS = (
        (("Text", "VIEW_3D", "WINDOW", False), (  # (1)
            ({"idname": Intellisense.bl_idname,
              "type": 'SPACE', "value": 'PRESS', "ctrl": True},  # (2)
             ()),  # (3)
            ({"idname": TestLine.bl_idname,
              "type": 'RET', "value": 'PRESS'},  # (2)
             ()),  # (3)
        )),
    )
    
    
    classes = (
        Intellisense,
        Intellioptions,
        Intellimenu,
        DebugPanel,
        TestLine,
        SetBreakPoint,
        Debug,
        )
    
        for cls in classes:
            bpy.utils.register_class(cls)
    
        keyconfig_utils.addon_keymap_register(bpy.context.window_manager, KEYMAPS)
        # note: these keys were assigned and never removed
        # moved to the new system introduced in 2.78.x (the old function is still here)
        # assignKey("Text", "text.intellisense", "SPACE", "Press", ["ctrl"])
        # assignKey("Text", "text.test_line", "RET", "Press", [], "", 1)
    
        keyconfig_utils.addon_keymap_unregister(bpy.context.window_manager, KEYMAPS)
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
    
    
    if __name__ == "__main__":