Displaying text inside an addon Panel This question is not about displaying text-like objects or mesh in the 3D viewport but rather about displaying plain text in a panel created by an Addon. 

I have a panel with two rows. On the first row is a button (Action 1) linked to an operator which generates some long text (say a ~1000 character error message). I want to display this generated text in the second row (with wrapping instead of truncation or ellipsis). I  also want to allow the user to select some or all of that text.  

My current attempt

I'm unable to come up with anything better than using the label attribute to display the long text (code below). But it doesnt satisfy my requirements: (1) Doesnt allow text selection (2) doesnt display full text (just displays first and last few characters with ellipsis in between)

Ideally I'd like to have a display similar to the box for "Vertex Groups" below



Of course, I'd want the ability to change the label "Vertex Groups" and to be able to populate the box with (wraparound) text. 

code

from bpy.props import StringProperty    
import bpy    

class MESH_OT_action1(bpy.types.Operator):
    bl_idname = 'mesh.action1'
    bl_label = 'Action 1'
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        try:
            context.scene.long_string = 'x' * 1000
            return {'FINISHED'}
        except Exception as e:
            self.report({'ERROR'}, e.args)
            return {'CANCELLED'}


class PANEL_PT_MyPanel(bpy.types.Panel):
    bl_idname = 'PANEL_PT_MyPanel'
    bl_label = 'mypanel'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Panel test'

    def draw(self, context):
        row_action_1_btn = self.layout.row()
        row_action_1_btn.operator('mesh.action1', icon='WORLD_DATA', text='Action 1')

        # Text area
        row_text = self.layout.row()
        text = context.scene.long_string
        row_text.label(text=text, icon='WORLD_DATA')


bl_info = {
    "name": "Textbox",
    "author": "author@gmail.com",
    "version": (0, 0, 1),
    "blender": (2, 80, 0),
    "location": "View3D",
    "description": "Demo Addon",
    "category": "Development",
}


def register():
    bpy.utils.register_class(MESH_OT_action1)
    bpy.utils.register_class(PANEL_PT_MyPanel)
    bpy.types.Scene.long_string = StringProperty(name='long_string', default='')


def unregister():
    bpy.utils.unregister_class(MESH_OT_action1)
    bpy.utils.unregister_class(PANEL_PT_MyPanel)
    del bpy.types.Scene.long_string


if __name__ == "__main__":
    register()
Log Window

Example using an UIList based on the answer of Create an interface which is similar to the material list box as suggested in the comments. Removed all superfluous operators to get a kind of log window.



# ##### 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 LICENSE BLOCK #####

bl_info = {
    "name": "log-uilist-dev",
    "description": "",
    "author": "p2or, brockmann",
    "version": (0, 1),
    "blender": (2, 80, 0),
    "location": "Text Editor",
    "warning": "", # used for warning icon and text in addons panel
    "wiki_url": "",
    "tracker_url": "",
    "category": "Development"
}

import bpy

from bpy.props import (IntProperty,
                       BoolProperty,
                       StringProperty,
                       EnumProperty,
                       CollectionProperty)

from bpy.types import (Operator,
                       Panel,
                       PropertyGroup,
                       UIList)

# -------------------------------------------------------------------
#   Operators
# -------------------------------------------------------------------

class CUSTOM_OT_actions(Operator):
    """Move items up and down, add and remove"""
    bl_idname = "custom.list_action"
    bl_label = "List Actions"
    bl_description = "Move items up and down, add and remove"
    bl_options = {'REGISTER'}

    action: EnumProperty(
        items=(
            ('UP', "Up", ""),
            ('DOWN', "Down", ""),
            ('REMOVE', "Remove", ""),
            ('ADD', "Add", "")))

    list_item: StringProperty()

    def invoke(self, context, event):
        scn = context.scene
        idx = scn.custom_index

        try:
            item = scn.custom[idx]
        except IndexError:
            pass
        else:
            if self.action == 'DOWN' and idx &lt; len(scn.custom) - 1:
                item_next = scn.custom[idx+1].name
                scn.custom.move(idx, idx+1)
                scn.custom_index += 1
                info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
                self.report({'INFO'}, info)

            elif self.action == 'UP' and idx &gt;= 1:
                item_prev = scn.custom[idx-1].name
                scn.custom.move(idx, idx-1)
                scn.custom_index -= 1
                info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
                self.report({'INFO'}, info)

            elif self.action == 'REMOVE':
                info = 'Item "%s" removed from list' % (scn.custom[idx].name)
                scn.custom_index -= 1
                scn.custom.remove(idx)
                self.report({'INFO'}, info)

        if self.action == 'ADD':
            if self.list_item:
                item = scn.custom.add()
                item.name = self.list_item
                item.coll_id = len(scn.custom)
                scn.custom_index = len(scn.custom)-1
                info = '"%s" added to list' % (item.name)
                self.report({'INFO'}, info)

        return {"FINISHED"}


class CUSTOM_OT_clearList(Operator):
    """Clear all items of the list"""
    bl_idname = "custom.clear_list"
    bl_label = "Clear List"
    bl_description = "Clear all items of the list"
    bl_options = {'INTERNAL'}

    @classmethod
    def poll(cls, context):
        return bool(context.scene.custom)

    def invoke(self, context, event):
        return context.window_manager.invoke_confirm(self, event)

    def execute(self, context):
        if bool(context.scene.custom):
            context.scene.custom.clear()
            self.report({'INFO'}, "All items removed")
        else:
            self.report({'INFO'}, "Nothing to remove")
        return{'FINISHED'}


# -------------------------------------------------------------------
#   Drawing
# -------------------------------------------------------------------

class CUSTOM_UL_items(UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        split = layout.split(factor=0.2)
        split.label(text="%d" % (index))
        #split.prop(item, "name", text="", emboss=False, translate=False, icon=custom_icon)
        split.label(text=item.name) # avoids renaming the item by accident

    def invoke(self, context, event):
        pass   

class CUSTOM_PT_objectList(Panel):
    """Adds a custom panel to the TEXT_EDITOR"""
    bl_idname = 'TEXT_PT_my_panel'
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    bl_label = "Log UIlist"

    def draw(self, context):
        layout = self.layout
        scn = bpy.context.scene

        rows = 7
        row = layout.row()
        row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows)

        row = layout.row()
        add = row.operator(CUSTOM_OT_actions.bl_idname, icon="ADD", text="Add Log Line")
        add.list_item = "Error %d" % (len(scn.custom))
        add.action = 'ADD'

        row = layout.row().operator("custom.clear_list", icon="X")


# -------------------------------------------------------------------
#   Collection
# -------------------------------------------------------------------

class CUSTOM_objectCollection(PropertyGroup):
    #name: StringProperty() -&gt; Instantiated by default
    coll_type: StringProperty()
    coll_id: IntProperty()


# -------------------------------------------------------------------
#   Register &amp; Unregister
# -------------------------------------------------------------------

classes = (
    CUSTOM_OT_actions,
    CUSTOM_OT_clearList,
    CUSTOM_UL_items,
    CUSTOM_objectCollection,
    CUSTOM_PT_objectList
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    # Custom scene properties
    bpy.types.Scene.custom = CollectionProperty(type=CUSTOM_objectCollection)
    bpy.types.Scene.custom_index = IntProperty()


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)

    del bpy.types.Scene.custom
    del bpy.types.Scene.custom_index


if __name__ == "__main__":
    register()


Note: the naming of the classes and variables might be off a little.