Improving the Blender IDE

This video shows the script that I’m working.
[video]https://pbs.twimg.com/tweet_video/CFnnSLlWYAAZeU8.mp4[/video]

It’s still a very poor script, needs many upgrades.
But I will let it here for those who want to support :).


import bpy
import bgl
import blf


def update_locs(self):
    top = self.space.top
    vlines = self.space.visible_lines
    vrange = range(top, top+vlines)
    self.loc_co = set()
    for i, loc in enumerate(self.locs):
        if loc[0] in vrange:
            coords = self.space.region_location_from_cursor(*loc)
            self.loc_co.add((i, coords))


def update_text(self, ref_text, IDE, hide = {-1}):
    self.locs = []
    iddef = 0
    lsp = 100
    hidden_lines = 0
    for i, l in enumerate(ref_text.lines):
        text = l.body


        if text != '' and not text.isspace():
            sp = 0
            while text[sp] == ' ':
                sp += 1


        fdef = text.find('def ')
        fclass = text.find('class ')


        if fdef != -1 and sp == fdef or\
           fclass != -1 and sp == fclass:


            self.locs.append((i-hidden_lines, sp+2))
            if iddef in hide:
                lsp = sp
            else:
                lsp = 100
            iddef +=1


        if sp <= lsp:
            if i == 0:
                IDE.write(text)
            else:
                IDE.write('
'+text)
        else:
            hidden_lines += 1


def navigation(self, context, event):
    rv3d = context.region_data
    if not hasattr(self, 'navigation_cache'): # or self.navigation_cache == False:
        self.navigation_cache = True
        self.scroll = []
        self.scroll_bar = []
        for key in context.window_manager.keyconfigs.user.keymaps['Text'].keymap_items:
            if key.idname == 'text.scroll':
                if not key.properties.is_property_set('lines'):
                    self.scroll.append((key.alt, key.ctrl, key.shift, key.type, key.value, 0))
                else:
                    self.scroll.append((key.alt, key.ctrl, key.shift, key.type, key.value, key.properties.lines))
            elif key.idname == 'text.scroll_bar':
                self.scroll_bar.append((key.alt, key.ctrl, key.shift, key.type, key.value))


    evkey = (event.alt, event.ctrl, event.shift, event.type, event.value)
    for key in self.scroll:
        if evkey == key[0:5]:
            if key[5] != 0:
                bpy.ops.text.scroll(lines=key[5])
            else:
                bpy.ops.text.scroll('INVOKE_DEFAULT')
            update_locs(self)
            break
    for key in self.scroll_bar:
        if evkey == key:
            bpy.ops.text.scroll_bar('INVOKE_DEFAULT')
            update_locs(self)
            break


class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "text.modal_operator"
    bl_label = "Simple Modal View3D Operator"


    def modal(self, context, event):
        rx, ry = context.region.width, context.region.height
        x, y = event.mouse_region_x, event.mouse_region_y
        if x < 0 or y < 0 or x - rx > 0 or y - ry > 0:
            return {'PASS_THROUGH'}
        else:
            #context.area.tag_redraw()
            self.update = navigation(self, context, event)


            if event.type in {'MOUSEMOVE', 'LEFTMOUSE'}:
                di = 10
                for i, loc in self.loc_co:
                    if abs(x - loc[0]) < di and abs(y - loc[1]-15) < di:
                        context.window.cursor_set("DEFAULT")
                        if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
                            if i not in self.hide:
                                self.hide.add(i)
                            else:
                                self.hide.remove(i)


                            IDE = self.space.text
                            IDE.clear()
                            update_text(self, self.text, IDE, hide = self.hide)
                            update_locs(self)
                        break
                else:
                    context.window.cursor_set("TEXT")


            if event.type in {'RIGHTMOUSE', 'ESC'}:
                bpy.data.texts.remove(self.space.text)
                self.space.text = self.text


                return {'CANCELLED'}


            return {'RUNNING_MODAL'}


    def invoke(self, context, event):
        area = context.area
        if area.type == 'TEXT_EDITOR':
            for space in area.spaces:
                if space.type == 'TEXT_EDITOR':
                    self.text = space.text
                    IDE = bpy.data.texts.new('IDE_'+'\"'+self.text.name+'\"')
                    self.hide = set()
                    update_text(self, self.text, IDE)


                    self.loc_co = set()
                    space.text = IDE
                    self.space = space
                    update_locs(self)
                    break


            self.update = True


            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Text Editor not found, cannot run operator")
            return {'CANCELLED'}


class EDITOR_Panel(bpy.types.Panel):
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    bl_label = "Editor"


    def draw(self, context):
        layout = self.layout
        layout.operator("text.modal_operator", text = "IDE")


def register():
    bpy.utils.register_class(ModalDrawOperator)
    bpy.utils.register_class(EDITOR_Panel)


def unregister():
    bpy.utils.unregister_class(ModalDrawOperator)
    bpy.utils.unregister_class(EDITOR_Panel)


if __name__ == "__main__":
    register()
    bpy.ops.text.modal_operator('INVOKE_DEFAULT')

It is in this sub-forum because it’s still incomplete.

Handy, great job!

Interestingly.