;;; godot-rc.el --- Remote control Godot from within Emacs -*- lexical-binding: t -*- ;; This file is not part of GNU Emacs ;; 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 3 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, see . ;;; Commentary: ;; This mode is intended to be used alongside the existing gdscript-modes, ;; and works to supplement them by introducing functionality such as scene ;; manipulation and editing. ;;; Code: (require 'magit-section) (defcustom godot-rc-host "127.0.0.1" "Host IP address for the Godot engine running godot-rc." :type 'string :group 'godot-rc) (defcustom godot-rc-port 6009 "Port for the Godot engine running godot-rc." :type 'number :group 'godot-rc) (defvar godot-rc-selected-scene nil "The currently selected scene - set automatically when visiting a .tscn file.") (defvar godot-rc-process nil "The network process for the remote control connection.") (defvar godot-rc-project-root nil "The project root of the active remote control connection.") (defvar godot-rc-scene-tree-buffer nil "The buffer that contains the scene tree.") (defun open-scene-tree-buffer () "Create and/or open the scene tree buffer." (setq godot-rc-scene-tree-buffer (get-buffer-create "*scene-tree*")) (with-current-buffer godot-rc-scene-tree-buffer (scene-tree-mode))) (defun godot-rc-process-filter-function (proc resp) "Handle RESP responses from the Godot PROC." (let ((response (json-parse-string resp :object-type 'plist :array-type 'list))) (cond ((equal "get-scene-tree" (plist-get response :for-cmd)) (scene-tree-render (plist-get response :value)))))) (defun initialize-godot-rc () "Initialize godot-rc using the current buffer to find the Godot project." (interactive) (setq godot-rc-project-root (locate-dominating-file default-directory "project.godot")) (setq godot-rc-process (open-network-stream "godot-rc" "*godot-rc-stream*" godot-rc-host godot-rc-port)) (set-process-filter godot-rc-process #'godot-rc-process-filter-function) (open-scene-tree-buffer)) (defun kill-godot-rc () "Stop the godot-rc process and reset all variables to nil." (interactive) (delete-process godot-rc-process) (kill-buffer godot-rc-scene-tree-buffer) (setq godot-rc-selected-scene nil godot-rc-process nil godot-rc-project-root nil godot-rc-scene-tree-buffer nil)) (defun godot-rc-send-command (cmd-plist) "Send the CMD-PLIST as a JSON string to Godot." (when godot-rc-process (process-send-string godot-rc-process (json-serialize cmd-plist)))) ;; TODO: this needs to check if file-name is in the godot-rc-project-root and turn it into a res:// path (defun godot-rc-open-scene (file-name &optional quiet) "Open FILE-NAME as a scene in Godot." (godot-rc-send-command `(:command "open-scene" :scene ,file-name :get-result ,(if quiet :false t))) (setq godot-rc-selected-scene file-name)) (defun tscn-check-visibility (win) "When WIN changes to a TSCN, tell Godot to open the scene." (let ((buf (current-buffer))) (when (equal (file-name-extension (buffer-file-name buf)) "tscn") (godot-rc-open-scene (buffer-file-name buf))))) (add-hook 'window-buffer-change-functions #'tscn-check-visibility) (add-hook 'window-selection-change-functions #'tscn-check-visibility) ;;; Scene-tree-mode and relevant commands (defclass scene-tree-node-section (magit-section) (;(keymap :initform 'org-roam-node-map) (node :initform nil)) "A `magit-section' used by `org-roam-mode' to outline NODE in its own heading.") (defun insert-node (node depth) "Insert a NODE and it's children as magit sections." (magit-insert-section (scene-tree-node-section node) (magit-insert-heading (concat (make-string (* depth 2) ?\s) (propertize (plist-get node :name) 'face 'font-lock-function-call-face) " : " (propertize (plist-get node :type) 'face 'font-lock-type-face) "\n")) (dolist (n (plist-get node :children)) (insert-node n (+ 1 depth))))) (defun scene-tree-render (tree) "Render the scene tree." (with-current-buffer godot-rc-scene-tree-buffer (let ((inhibit-read-only t)) (erase-buffer) (magit-insert-section (magit-section "SceneTree") (magit-insert-heading (file-name-nondirectory godot-rc-selected-scene) "\n\n") (insert-node tree 0)) (goto-char 0)))) (defun scene-tree-rename-at-point (name) "Rename node at point." (interactive "sNew name: ") (let ((node-path (plist-get (slot-value (magit-current-section) 'value) :path))) (godot-rc-send-command `(:command "set-node-name" :path ,node-path :new-name ,name)))) (defvar scene-tree-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map magit-section-mode-map) (define-key map [C-return] 'scene-tree-rename-at-point) map "Keymap for scene-tree-mode")) (define-derived-mode scene-tree-mode magit-section-mode "Scene Tree" "Mode for showing the scene tree." :group 'godot-rc) (provide 'godot-rc) ;;; godot-rc.el ends here