diff --git a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd new file mode 100644 index 0000000..a4ba159 --- /dev/null +++ b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd @@ -0,0 +1,78 @@ +tool +extends "res://addons/godot_xterm/nodes/terminal/terminal.gd" + +var editor_settings: EditorSettings +var timer := Timer.new() + +onready var pty = $PTY + + +# Sets terminal colors according to a dictionary that maps terminal color names +# to TextEditor theme color names. +func _set_terminal_colors(color_map: Dictionary) -> void: + for key in color_map.keys(): + var val: String = color_map[key] + var color: Color = editor_settings.get_setting("text_editor/highlighting/%s" % val) + theme.set_color(key, "Terminal", color) + + +func _ready(): + if not editor_settings: + return + + theme = Theme.new() + + # Use the same font as EditorLog. + theme.default_font = get_font("output_source", "EditorFonts") + + # Get colors from TextEdit theme. Created using the default (Adaptive) theme + # for reference, but will probably cause strange results if using another theme + # better to use a dedicated terminal theme, rather than relying on this. + _set_terminal_colors( + { + "Black": "caret_background_color", + "Red": "keyword_color", + "Green": "gdscript/node_path_color", + "Yellow": "string_color", + "Blue": "function_color", + "Magenta": "symbol_color", + "Cyan": "gdscript/function_definition_color", + "Dark Grey": "comment_color", + "Light Grey": "text_color", + "Light Red": "breakpoint_color", + "Light Green": "base_type_color", + "Light Yellow": "search_result_color", + "Light Blue": "member_variable_color", + "Light Magenta": "code_folding_color", + "Light Cyan": "user_type_color", + "White": "text_selected_color", + "Background": "background_color", + "Foreground": "caret_color", + } + ) + _native_terminal._update_theme() + + # In editor _process is not called continuously unless the "Update Continuously" + # editor setting is enabled. This setting is disabled by default and uses 100% + # of one core when enabled, so best to leave it off and use a timer instead. + add_child(timer) + timer.wait_time = 0.025 + timer.connect("timeout", self, "_poll") + timer.start() + + +func _poll(): + if pty and pty._pipe: + pty._pipe.poll() + update() + + +func _input(event): + if not has_focus(): + return + + # We need to handle many input events otherwise keys such as TAB, ctrl, etc. + # will trigger editor shortcuts when using them in the terminal. + if event is InputEventKey: + get_tree().set_input_as_handled() + _gui_input(event) diff --git a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn new file mode 100644 index 0000000..2ad84f0 --- /dev/null +++ b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd" type="Script" id=1] +[ext_resource path="res://addons/godot_xterm/nodes/pty/unix/pty_unix.gd" type="Script" id=2] + +[node name="Terminal" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +focus_mode = 1 +size_flags_horizontal = 4 +size_flags_vertical = 4 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} +copy_on_selection = true + +[node name="PTY" type="Node" parent="."] +script = ExtResource( 2 ) +terminal_path = NodePath("..") +env = { +"COLORTERM": "truecolor", +"TERM": "xterm-256color" +} diff --git a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd new file mode 100644 index 0000000..7731d3a --- /dev/null +++ b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd @@ -0,0 +1,110 @@ +# Copyright (c) 2021, Leroy Hopson (MIT License). +# +# This file contains snippets of code derived from Godot's editor_node.cpp file. +# These snippets are copyright of their authors and released under the MIT license: +# - Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur (MIT License). +# - Copyright (c) 2014-2021 Godot Engine contributors (MIT License). +tool +extends Control + +const EditorTerminal := preload("./editor_terminal.tscn") + +# Has access to the EditorSettings singleton so it can dynamically generate the +# terminal color scheme based on editor theme settings. +var editor_interface: EditorInterface + +onready var editor_settings: EditorSettings = editor_interface.get_editor_settings() +onready var tabs: Tabs = $VBoxContainer/TabbarContainer/Tabs +onready var tabbar_container: HBoxContainer = $VBoxContainer/TabbarContainer +onready var add_button: ToolButton = $VBoxContainer/TabbarContainer/Tabs/AddButton +onready var tab_container: TabContainer = $VBoxContainer/TabContainer +onready var ready := true + +var _theme := Theme.new() +var _tab_container_min_size + + +func _ready(): + tab_container.add_stylebox_override("panel", get_stylebox("Background", "EditorStyles")) + _update_settings() + + +func _update_settings() -> void: + var editor_scale: float = editor_interface.get_editor_scale() + rect_min_size = Vector2(0, tabbar_container.rect_size.y + 182) * editor_scale + + # Use the same policy as editor scene tabs. + tabs.tab_close_display_policy = ( + Tabs.CLOSE_BUTTON_SHOW_ALWAYS + if editor_settings.get_setting("interface/scene_tabs/always_show_close_button") + else Tabs.CLOSE_BUTTON_SHOW_ACTIVE_ONLY + ) + + _update_terminal_tabs() + + +func _update_terminal_tabs(): + # Wait a couple of frames to allow everything to resize before updating. + yield(get_tree(), "idle_frame") + yield(get_tree(), "idle_frame") + + if tabs.get_offset_buttons_visible(): + # Move add button to fixed position on the tabbar. + if add_button.get_parent() == tabs: + add_button.rect_position = Vector2.ZERO + tabs.remove_child(add_button) + tabbar_container.add_child(add_button) + tabbar_container.move_child(add_button, 0) + else: + # Move add button after last tab. + if add_button.get_parent() == tabbar_container: + tabbar_container.remove_child(add_button) + tabs.add_child(add_button) + var last_tab := Rect2() + if tabs.get_tab_count() > 0: + last_tab = tabs.get_tab_rect(tabs.get_tab_count() - 1) + add_button.rect_position = Vector2( + last_tab.position.x + last_tab.size.x + 3, last_tab.position.y + ) + + # Make sure we still own the button, so it gets saved with our scene. + add_button.owner = self + + +func _on_AddButton_pressed(): + var shell = OS.get_environment("SHELL") if OS.has_environment("SHELL") else "sh" + var terminal := EditorTerminal.instance() + tabs.add_tab(shell.get_file()) + terminal.editor_settings = editor_settings + terminal.set_anchors_preset(PRESET_WIDE) + tab_container.add_child(terminal) + terminal.pty.fork(shell) + terminal.grab_focus() + tabs.current_tab = tabs.get_tab_count() - 1 + tab_container.current_tab = tabs.current_tab + _update_terminal_tabs() + + +func _on_Tabs_tab_changed(tab_index): + tab_container.current_tab = tab_index + tab_container.get_child(tab_index).grab_focus() + + +func _on_Tabs_tab_close(tab_index): + tabs.remove_tab(tab_index) + tab_container.get_child(tab_index).queue_free() + _update_terminal_tabs() + + +func _notification(what): + if not ready: + return + + match what: + EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: + _update_settings() + _update_terminal_tabs() + NOTIFICATION_RESIZED: + _update_terminal_tabs() + NOTIFICATION_WM_FOCUS_IN: + _update_terminal_tabs() diff --git a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn new file mode 100644 index 0000000..d672c12 --- /dev/null +++ b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn @@ -0,0 +1,100 @@ +[gd_scene load_steps=7 format=2] + +[ext_resource path="res://addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd" type="Script" id=1] + +[sub_resource type="Image" id=6] +data = { +"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), +"format": "LumAlpha8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id=2] +flags = 4 +flags = 4 +image = SubResource( 6 ) +size = Vector2( 16, 16 ) + +[sub_resource type="StyleBoxTexture" id=3] +texture = SubResource( 2 ) +region_rect = Rect2( 0, 0, 16, 16 ) +margin_left = 2.0 +margin_right = 2.0 +margin_top = 2.0 +margin_bottom = 2.0 + +[sub_resource type="Image" id=7] +data = { +"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id=5] +flags = 0 +flags = 0 +image = SubResource( 7 ) +size = Vector2( 16, 16 ) + +[node name="Panel" type="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_top = -3.0 +rect_min_size = Vector2( 0, 34 ) +custom_styles/panel = SubResource( 3 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 0, 24 ) +custom_constants/separation = 0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TabbarContainer" type="HBoxContainer" parent="VBoxContainer"] +margin_right = 1024.0 +margin_bottom = 24.0 + +[node name="Tabs" type="Tabs" parent="VBoxContainer/TabbarContainer"] +margin_right = 1024.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +tab_align = 0 +tab_close_display_policy = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="AddButton" type="ToolButton" parent="VBoxContainer/TabbarContainer/Tabs"] +margin_left = 3.0 +margin_right = 31.0 +margin_bottom = 24.0 +hint_tooltip = "Add a new scene." +icon = SubResource( 5 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TabContainer" type="TabContainer" parent="VBoxContainer"] +margin_top = 24.0 +margin_right = 1024.0 +margin_bottom = 603.0 +rect_clip_content = true +size_flags_vertical = 3 +custom_styles/panel = SubResource( 3 ) +custom_constants/top_margin = 0 +custom_constants/side_margin = 0 +tabs_visible = false + +[connection signal="tab_changed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_changed"] +[connection signal="tab_close" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_close"] +[connection signal="pressed" from="VBoxContainer/TabbarContainer/Tabs/AddButton" to="." method="_on_AddButton_pressed"] diff --git a/addons/godot_xterm/native/godotxtermnative.gdnlib b/addons/godot_xterm/native/godotxtermnative.gdnlib index 77f5fe1..3adb2f8 100644 --- a/addons/godot_xterm/native/godotxtermnative.gdnlib +++ b/addons/godot_xterm/native/godotxtermnative.gdnlib @@ -3,7 +3,7 @@ singleton=false load_once=true symbol_prefix="godot_" -reloadable=true +reloadable=false [entry] diff --git a/addons/godot_xterm/nodes/terminal/terminal.gd b/addons/godot_xterm/nodes/terminal/terminal.gd index 98384d6..d2c06c4 100644 --- a/addons/godot_xterm/nodes/terminal/terminal.gd +++ b/addons/godot_xterm/nodes/terminal/terminal.gd @@ -108,6 +108,7 @@ func _ready(): func _refresh(): + _screen.update() if update_mode == UpdateMode.AUTO: _native_terminal.update_mode = UpdateMode.ALL_NEXT_FRAME diff --git a/addons/godot_xterm/plugin.gd b/addons/godot_xterm/plugin.gd index 2a9c86d..f6b5222 100644 --- a/addons/godot_xterm/plugin.gd +++ b/addons/godot_xterm/plugin.gd @@ -3,6 +3,7 @@ extends EditorPlugin var pty_supported := OS.get_name() in ["X11", "Server", "OSX"] var asciicast_import_plugin +var terminal_panel: Control func _enter_tree(): @@ -23,6 +24,9 @@ func _enter_tree(): "X11", "Server", "OSX": pty_script = load("res://addons/godot_xterm/nodes/pty/unix/pty_unix.gd") add_custom_type("PTY", "Node", pty_script, pty_icon) + terminal_panel = preload("./editor_plugins/terminal/terminal_panel.tscn").instance() + terminal_panel.editor_interface = get_editor_interface() + add_control_to_bottom_panel(terminal_panel, "Terminal") func _exit_tree(): @@ -34,3 +38,5 @@ func _exit_tree(): if pty_supported: remove_custom_type("PTY") + remove_control_from_bottom_panel(terminal_panel) + terminal_panel.free()