From cfeba7e72b608275e2afd42d544459423ef491b7 Mon Sep 17 00:00:00 2001 From: Josef Vlach Date: Sun, 31 May 2020 20:32:07 +0100 Subject: [PATCH] Multiple projects support - comint-mode buffer for stdout/stderr --- CHANGELOG.md | 2 + gdscript-comint.el | 100 +++++++++++++++++++++++++++++++++++++++++++++ gdscript-godot.el | 73 +++++++++++++++------------------ gdscript-hydra.el | 23 +++++++---- gdscript-utils.el | 23 +++++++++-- 5 files changed, 169 insertions(+), 52 deletions(-) create mode 100644 gdscript-comint.el diff --git a/CHANGELOG.md b/CHANGELOG.md index 150d55c..067f1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This document lists new features, improvements, changes, and bug fixes in each r - Support for running the project and scenes with [hydra](https://github.com/abo-abo/hydra) with `gdscript-hydra-show`. - Add the ability to open a local copy of the Godot docs with `gdscript-docs-*` commands. +- Multiple projects support. Every project's process run in its own buffer. +- Godot's stdout/stderr now goes to comint buffer. It has support for quick navigation from errors to file locations. ### Bug fixes diff --git a/gdscript-comint.el b/gdscript-comint.el new file mode 100644 index 0000000..b981dfa --- /dev/null +++ b/gdscript-comint.el @@ -0,0 +1,100 @@ +;;; gdscript-comint.el --- Support for comint mode -*- lexical-binding: t; -*- +;; +;; Copyright (C) 2020 GDQuest and contributors +;; +;; Author: Josef Vlach +;; URL: https://github.com/GDQuest/emacs-gdscript-mode/ +;; Version: 1.0.0 +;; Package-Requires: ((emacs "26.3")) +;; Maintainer: nathan@gdquest.com +;; Created: May 2020 +;; Keywords: languages +;; +;; 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: +;; +;; godot-mode for handling stdout and stderr from godot executable. +;; +;; It support quick navigation from errors to file location. +;; +;;; Code: + +(require 'ansi-color) +(require 'comint) +(require 'compile) +(require 'gdscript-utils) + +(defvar gdscript-comint--mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map + (make-composed-keymap compilation-shell-minor-mode-map + comint-mode-map)) + (define-key map (kbd "C-a") 'comint-bol) + (define-key map (kbd "C-c r") 'gdscript-hydra-show) + map) + "Basic mode map for `godot-mode'.") + +(defun gdscript-comint--run (arguments) + "Run godot in comint mode. + +ARGUMENTS are command line arguments for godot executable. +When run it will kill existing process if one exists." + (let ((buffer-name (gdscript-util--get-godot-buffer-name)) + (inhibit-read-only 1)) + + (when (not (executable-find gdscript-godot-executable)) + (error "Error: Could not find %s on PATH. Please customize the gdscript-godot-executable variable" gdscript-godot-executable)) + + ;; start new godot + (with-current-buffer (get-buffer-create buffer-name) + (unless (derived-mode-p 'godot-mode) + (godot-mode) + (buffer-disable-undo)) + (erase-buffer) + (comint-exec (current-buffer) buffer-name gdscript-godot-executable nil arguments)))) + +(define-derived-mode godot-mode comint-mode "godot" + "Major mode for godot. + +\\{gdscript-comint--mode-map}" + (use-local-map gdscript-comint--mode-map) + (add-hook 'godot-mode-hook 'gdscript-comint--initialize-for-comint-mode) + (add-hook 'godot-mode-hook 'gdscript-comint--initialize-for-compilation-mode)) + +(defun gdscript-comint--initialize-for-comint-mode () + "Initialize buffer for comint mode support." + (when (derived-mode-p 'comint-mode) + (setq comint-process-echoes nil) + (setq comint-prompt-regexp "debug> ") + (setq-local comint-use-prompt-regexp t) + (setq-local comint-prompt-read-only t) + (setq-local comint-buffer-maximum-size 4096) + (setq-local comint-output-filter-functions '(ansi-color-process-output comint-postoutput-scroll-to-bottom)) + (setq ansi-color-for-comint-mode t))) + +(defun gdscript-comint--initialize-for-compilation-mode () + "Initialize buffer for compilation mode support." + (setq-local + compilation-error-regexp-alist + '( + ("^ At: res://\\([[:word:]\/]+.gd\\):\\([[:digit:]]+\\)." 1 2 nil 2 1) + ("^*Frame [[:digit:]]+ - res://\\([[:word:]\/]+.gd\\):\\([[:digit:]]+\\)." 1 2 nil 2 1))) + (setq-local compilation-mode-font-lock-keywords nil) + (compilation-setup t)) + +(provide 'gdscript-comint) +;;; gdscript-comint.el ends here diff --git a/gdscript-godot.el b/gdscript-godot.el index 134d633..ab588cf 100644 --- a/gdscript-godot.el +++ b/gdscript-godot.el @@ -32,19 +32,20 @@ ;; ;;; Code: +(require 'gdscript-comint) (require 'gdscript-customization) (require 'gdscript-utils) ;;;###autoload -(defvar gdscript-godot--debug-options-hydra nil) +(defvar gdscript-godot--debug-options-hydra :not-list) (defvar gdscript-godot--debug-selected-option 1) (defvar gdscript-godot--debug-options-alist - '((1 . "") - (2 . "--debug-collisions") - (3 . "--debug-navigation") - (4 . "--debug-collisions --debug-navigation"))) + '((1 . ()) + (2 . ("--debug-collisions")) + (3 . ("--debug-navigation")) + (4 . ("--debug-collisions" "--debug-navigation")))) (defmacro gdscript-godot--debug-options-handler (debug-options &rest body) "Set debug-options either as set by Hydra, or use one provided by prefix argument selection." @@ -54,42 +55,36 @@ (gdscript-godot--change-debug-options) gdscript-godot--debug-selected-option)) (prefix-options (cdr (assoc debug-option-index gdscript-godot--debug-options-alist))) - (use-hydra-options (not (booleanp gdscript-godot--debug-options-hydra))) ;; gdscript-godot--debug-options-hydra is a string when run from hydra + (use-hydra-options (listp gdscript-godot--debug-options-hydra)) ;; gdscript-godot--debug-options-hydra is a list when run from hydra (,debug-options (if use-hydra-options gdscript-godot--debug-options-hydra prefix-options))) - ,@body - (setq gdscript-godot--debug-options-hydra nil))) + ,@body)) -(defun gdscript-godot--run-command (cmd &optional show) +(defun gdscript-godot--run-command (&rest arguments) "Run a Godot process. -CMD is the command to be invoked by the shell. If SHOW, the +CMD is the command to be invoked by the shell. The output of the process will be provided in a buffer named -`*godot*'." - (when (not (executable-find gdscript-godot-executable)) - (error "Error: Could not find %s on PATH. Please customize the gdscript-godot-executable variable." gdscript-godot-executable)) - - (start-process-shell-command "Godot Process" (if show - "*godot*" nil) cmd)) +`*godot - *'." + (gdscript-comint--run (gdscript-util--flatten (list (gdscript-godot--build-shell-command) arguments))) + (setq gdscript-godot--debug-options-hydra :not-list)) (defun gdscript-godot--build-shell-command (&optional path) "Build a shell command to with the Godot executable. If PATH is not provided, try to find it using the current file's directory as starting point." - (let* ((project-path (or path (gdscript-util--find-project-configuration-file)))) - (concat gdscript-godot-executable " --path " project-path))) + (let ((project-path (or path (gdscript-util--find-project-configuration-file)))) + (list "--path" project-path))) (defun gdscript-godot-open-project-in-editor () "Run Godot Engine Editor." (interactive) - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " -e"))) + (gdscript-godot--run-command "-e")) (defun gdscript-godot-run-project () "Run the current project in Godot Engine." (interactive) - (gdscript-godot--run-command - (gdscript-godot--build-shell-command))) + (gdscript-godot--run-command)) (defun gdscript-godot-run-project-debug () "Run the current project in Godot Engine. @@ -97,15 +92,12 @@ file's directory as starting point." When run with prefix argument, it offers extra debug options to choose from." (interactive) (gdscript-godot--debug-options-handler debug-options - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " -d " debug-options) t))) + (gdscript-godot--run-command "-d" debug-options))) (defun gdscript-godot-run-current-scene () "Run the current script file in Godot Engine." (interactive) - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " " - (gdscript-util--get-godot-project-file-path-relative buffer-file-name) ".tscn"))) + (gdscript-godot--run-command (gdscript-godot--scene-name))) (defun gdscript-godot-run-current-scene-debug () "Run the current script file in Godot Engine. @@ -113,17 +105,12 @@ When run with prefix argument, it offers extra debug options to choose from." When run with prefix argument, it offers extra debug options to choose from." (interactive) (gdscript-godot--debug-options-handler debug-options - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " -d " debug-options - (gdscript-util--get-godot-project-file-path-relative buffer-file-name) ".tscn") - t))) + (gdscript-godot--run-command "-d" debug-options (gdscript-godot--scene-name)))) (defun gdscript-godot-edit-current-scene () "Run the current script file in Godot Engine." (interactive) - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " -e " - (gdscript-util--get-godot-project-file-path-relative buffer-file-name) ".tscn"))) + (gdscript-godot--run-command "-e" (gdscript-godot--scene-name))) (defun gdscript-godot-run-current-script () "Run the current script file in Godot Engine. @@ -131,17 +118,23 @@ When run with prefix argument, it offers extra debug options to choose from." For this to work, the script must inherit either from \"SceneTree\" or \"MainLoop\"." (interactive) - (gdscript-godot--run-command - (concat (gdscript-godot--build-shell-command) " -s " (file-relative-name buffer-file-name)) - t)) + (gdscript-godot--run-command "-s" (file-relative-name buffer-file-name))) + +(defun gdscript-godot--scene-name () + "Return the name of current scene." + (concat (gdscript-util--get-godot-project-file-path-relative buffer-file-name) ".tscn")) + +(defun gdscript-godot--debug-options-to-string (index) + "Return debug options from `gdscript-godot--debug-options-alist' for given INDEX as a string." + (mapconcat 'identity (cdr (assoc index gdscript-godot--debug-options-alist)) " ")) (defun gdscript-godot--debug-options-collection () "Output a list of debug options to choose from by *-read function." (list (format "1) [%s] " (if (eq gdscript-godot--debug-selected-option 1) "X" " ")) - (format "2) [%s] %s" (if (eq gdscript-godot--debug-selected-option 2) "X" " ") (cdr (assoc 2 gdscript-godot--debug-options-alist))) - (format "3) [%s] %s" (if (eq gdscript-godot--debug-selected-option 3) "X" " ") (cdr (assoc 3 gdscript-godot--debug-options-alist))) - (format "4) [%s] %s" (if (eq gdscript-godot--debug-selected-option 4) "X" " ") (cdr (assoc 4 gdscript-godot--debug-options-alist))))) + (format "2) [%s] %s" (if (eq gdscript-godot--debug-selected-option 2) "X" " ") (gdscript-godot--debug-options-to-string 2)) + (format "3) [%s] %s" (if (eq gdscript-godot--debug-selected-option 3) "X" " ") (gdscript-godot--debug-options-to-string 3)) + (format "4) [%s] %s" (if (eq gdscript-godot--debug-selected-option 4) "X" " ") (gdscript-godot--debug-options-to-string 4)))) (defun gdscript-godot--read-debug-options () "Read debug options preference by user from mini-buffer." diff --git a/gdscript-hydra.el b/gdscript-hydra.el index 75ab969..f174dff 100644 --- a/gdscript-hydra.el +++ b/gdscript-hydra.el @@ -37,6 +37,7 @@ (require 'hydra nil t) (require 'gdscript-godot) +(require 'gdscript-utils) ;;;###autoload (defvar gdscript-hydra--debug nil) @@ -73,9 +74,10 @@ RUN-EDITOR is a function to call when editor flag is selected in hydra. It is setting variable `gdscript-godot--debug-options-hydra' based on hydra checkboxes." (setq gdscript-godot--debug-options-hydra - (concat - (when gdscript-hydra--debug-collisions "--debug-collisions ") - (when gdscript-hydra--debug-navigation "--debug-navigation "))) + (remove nil + (list + (when gdscript-hydra--debug-collisions "--debug-collisions") + (when gdscript-hydra--debug-navigation "--debug-navigation")))) (pcase project-or-scene (:project (gdscript-hydra--dispatch 'gdscript-godot-run-project @@ -87,12 +89,15 @@ It is setting variable `gdscript-godot--debug-options-hydra' based on hydra chec (:script (gdscript-godot-run-current-script)))) (defun gdscript-hydra--open-godot-buffer () - "Find buffer named *godot* and if it exists open it in other window." - (let ((godot-buffer (seq-find - (lambda (current-buffer) - (with-current-buffer current-buffer - (equal (buffer-name) "*godot*"))) (buffer-list)))) - (when godot-buffer (switch-to-buffer-other-window godot-buffer)))) + "Find buffer named *godot - * and if it exists open it in other window." + (let* ((current-name (buffer-name (current-buffer))) + (godot-buffer-name (gdscript-util--get-godot-buffer-name))) + (unless (string= godot-buffer-name current-name) + (let ((godot-buffer (seq-find + (lambda (current-buffer) + (with-current-buffer current-buffer + (equal (buffer-name) godot-buffer-name))) (buffer-list)))) + (when godot-buffer (switch-to-buffer-other-window godot-buffer)))))) (ignore-errors ;; Don't signal an error when hydra.el is not present diff --git a/gdscript-utils.el b/gdscript-utils.el index 02557f0..08002f5 100644 --- a/gdscript-utils.el +++ b/gdscript-utils.el @@ -100,9 +100,10 @@ starts from the current buffer path. WARNING: the Godot project must exist for this function to work." (let ((base-path (or start-path default-directory))) - (locate-dominating-file base-path - (lambda (parent) - (directory-files parent t "project.godot"))))) + (expand-file-name + (locate-dominating-file base-path + (lambda (parent) + (directory-files parent t "project.godot")))))) (defun gdscript-util--get-godot-project-name () "Retrieve the project name from Godot's configuration file." @@ -113,12 +114,28 @@ WARNING: the Godot project must exist for this function to work." (match-string 1) (error "Could not find the name of the project")))) +(defun gdscript-util--get-godot-buffer-name () + "Return buffer name for godot's stdout/stderr output." + (format "*godot - %s*" (gdscript-util--get-godot-project-name))) + (defun gdscript-util--get-godot-project-file-path-relative (file-path) "Return the relative path of `FILE-PATH' to Godot's configuration file." (concat (file-name-sans-extension (file-relative-name file-path (gdscript-util--find-project-configuration-file))))) +(defun gdscript-util--flatten (xs) + "Flatten deeply nested list. + +For example: +> (gdscript-util--flatten (list 1 2 (list 3 (list 4 5)) nil)) +> (1 2 3 4 5) +" + (cond + ((null xs) nil) + ((listp xs) (append (gdscript-util--flatten (car xs)) (gdscript-util--flatten (cdr xs)))) + (t (list xs)))) + (provide 'gdscript-utils) ;;; gdscript-utils.el ends here