mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-11-25 10:40:25 +01:00
6cd5facb98
As cols and rows are read only (i.e. automatically determined by rect and font size) there is no need for the properties to be exposed. Instead, users can get the calculated cols and rows using the get_cols() and get_rows() methods.
288 lines
9.2 KiB
GDScript
288 lines
9.2 KiB
GDScript
# 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")
|
|
const PTY := preload("../../pty.gd")
|
|
const TerminalSettings := preload("./settings/terminal_settings.gd")
|
|
|
|
const SETTINGS_FILE_PATH := "res://.gdxterm/settings.tres"
|
|
|
|
enum TerminalPopupMenuOptions {
|
|
NEW_TERMINAL = 0,
|
|
COPY = 2,
|
|
PASTE = 3,
|
|
COPY_ALL = 4,
|
|
CLEAR = 6,
|
|
KILL_TERMINAL = 7,
|
|
}
|
|
|
|
# Has access to the EditorSettings singleton so it can dynamically generate the
|
|
# terminal color scheme based on editor theme settings.
|
|
var editor_plugin: EditorPlugin
|
|
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 terminal_popup_menu: PopupMenu = $VBoxContainer/TerminalPopupMenu
|
|
|
|
# Size label.
|
|
# Used to show the size of the terminal (rows/cols) and panel (pixels) when resized.
|
|
onready var size_label: Label = $SizeLabel
|
|
onready var size_label_timer: Timer = $SizeLabel/SizeLabelTimer
|
|
|
|
onready var ready := true
|
|
|
|
var _theme := Theme.new()
|
|
var _settings: TerminalSettings
|
|
var _tab_container_min_size
|
|
|
|
|
|
func _ready():
|
|
tab_container.add_stylebox_override("panel", get_stylebox("Background", "EditorStyles"))
|
|
_update_settings()
|
|
|
|
|
|
func _load_or_create_settings() -> void:
|
|
# Use only default settings for now, until settings are properly defined
|
|
# and documented.
|
|
_settings = TerminalSettings.new()
|
|
return
|
|
|
|
var dir := Directory.new()
|
|
|
|
if not dir.dir_exists(SETTINGS_FILE_PATH.get_base_dir()):
|
|
dir.make_dir(SETTINGS_FILE_PATH.get_base_dir())
|
|
|
|
if not dir.file_exists(SETTINGS_FILE_PATH):
|
|
var settings := TerminalSettings.new()
|
|
ResourceSaver.save(SETTINGS_FILE_PATH, settings)
|
|
|
|
_settings = load(SETTINGS_FILE_PATH)
|
|
|
|
|
|
func _update_settings() -> void:
|
|
_load_or_create_settings()
|
|
|
|
var editor_scale: float = 1.0
|
|
if editor_interface.has_method("get_editor_scale"):
|
|
editor_scale = editor_interface.get_editor_scale()
|
|
|
|
rect_min_size = Vector2(0, tabbar_container.rect_size.y + 182) * editor_scale
|
|
rect_size.y = 415
|
|
|
|
tabs.tab_close_display_policy = Tabs.CLOSE_BUTTON_SHOW_ALWAYS
|
|
|
|
# Update shortcuts.
|
|
if _settings.new_terminal_shortcut:
|
|
terminal_popup_menu.set_item_shortcut(
|
|
TerminalPopupMenuOptions.NEW_TERMINAL, _settings.new_terminal_shortcut, true
|
|
)
|
|
if _settings.kill_terminal_shortcut:
|
|
terminal_popup_menu.set_item_shortcut(
|
|
TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false
|
|
)
|
|
if _settings.copy_shortcut:
|
|
terminal_popup_menu.set_item_shortcut(
|
|
TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false
|
|
)
|
|
if _settings.paste_shortcut:
|
|
terminal_popup_menu.set_item_shortcut(
|
|
TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false
|
|
)
|
|
|
|
_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)
|
|
terminal.connect("gui_input", self, "_on_TabContainer_gui_input")
|
|
terminal.connect("exited", self, "_on_Terminal_exited", [terminal])
|
|
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()
|
|
|
|
# Switch focus to the next active tab.
|
|
if tabs.get_tab_count() > 0:
|
|
tab_container.get_child(tabs.current_tab).grab_focus()
|
|
|
|
_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()
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if not _settings or not event.is_pressed():
|
|
return
|
|
|
|
# Global shortcut to open new terminal and make terminal panel visible.
|
|
if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.new_terminal_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
editor_plugin.make_bottom_panel_item_visible(self)
|
|
_on_AddButton_pressed()
|
|
|
|
# Non-global shortcuts, only applied if terminal is active and focused.
|
|
if (
|
|
tabs.get_tab_count() > 0 and tab_container.get_child(tabs.current_tab).has_focus()
|
|
or terminal_popup_menu.has_focus()
|
|
):
|
|
# Kill terminal.
|
|
if _settings.kill_terminal_shortcut and _settings.kill_terminal_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.kill_terminal_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL)
|
|
|
|
# Copy.
|
|
if _settings.copy_shortcut and _settings.copy_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.copy_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.COPY)
|
|
|
|
# Paste.
|
|
if _settings.paste_shortcut and _settings.paste_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.paste_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.PASTE)
|
|
|
|
# Next tab.
|
|
if _settings.next_tab_shortcut and _settings.next_tab_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.next_tab_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
tabs.current_tab = min(tabs.current_tab + 1, tabs.get_tab_count() - 1)
|
|
|
|
# Previous tab.
|
|
if _settings.previous_tab_shortcut and _settings.previous_tab_shortcut.shortcut:
|
|
if event.shortcut_match(_settings.previous_tab_shortcut.shortcut):
|
|
get_tree().set_input_as_handled()
|
|
tabs.current_tab = max(tabs.current_tab - 1, 0)
|
|
|
|
|
|
func _on_TabContainer_gui_input(event):
|
|
if event is InputEventMouseButton and event.button_index == BUTTON_RIGHT:
|
|
terminal_popup_menu.rect_position = event.global_position
|
|
terminal_popup_menu.popup()
|
|
|
|
|
|
func _on_TerminalPopupMenu_id_pressed(id):
|
|
match id:
|
|
TerminalPopupMenuOptions.NEW_TERMINAL:
|
|
_on_AddButton_pressed()
|
|
|
|
if tabs.get_tab_count() > 0:
|
|
var terminal = tab_container.get_child(tab_container.current_tab)
|
|
match id:
|
|
TerminalPopupMenuOptions.COPY:
|
|
OS.clipboard = terminal.copy_selection()
|
|
TerminalPopupMenuOptions.PASTE:
|
|
for i in OS.clipboard.length():
|
|
var event = InputEventKey.new()
|
|
event.unicode = ord(OS.clipboard[i])
|
|
event.pressed = true
|
|
terminal._gui_input(event)
|
|
TerminalPopupMenuOptions.COPY_ALL:
|
|
OS.clipboard = terminal.copy_all()
|
|
TerminalPopupMenuOptions.CLEAR:
|
|
terminal.clear()
|
|
TerminalPopupMenuOptions.KILL_TERMINAL:
|
|
_on_Tabs_tab_close(tabs.current_tab)
|
|
|
|
|
|
func _on_Tabs_reposition_active_tab_request(idx_to):
|
|
var active = tab_container.get_child(tab_container.current_tab)
|
|
tab_container.move_child(active, idx_to)
|
|
|
|
|
|
func _on_Panel_resized():
|
|
if not size_label:
|
|
return
|
|
|
|
var size = tab_container.rect_size
|
|
if tabs.get_tab_count() > 0:
|
|
var terminal = tab_container.get_child(tabs.current_tab)
|
|
var cols = terminal.get_cols()
|
|
var rows = terminal.get_rows()
|
|
size_label.text = "Size: %d cols; %d rows\n(%d x %d px)" % [cols, rows, size.x, size.y]
|
|
else:
|
|
size_label.text = "Size:\n(%d x %d px)" % [size.x, size.y]
|
|
|
|
size_label.visible = true
|
|
size_label_timer.wait_time = 1
|
|
size_label_timer.start()
|
|
|
|
|
|
func _on_SizeLabelTimer_timeout():
|
|
if size_label:
|
|
size_label.visible = false
|
|
|
|
|
|
func _on_Terminal_exited(exit_code, signum, terminal):
|
|
# Leave non-zero exit code terminals open in case they have some important
|
|
# error information.
|
|
if exit_code == 0:
|
|
_on_Tabs_tab_close(terminal.get_index())
|