Multiple projects support - comint-mode buffer for stdout/stderr

This commit is contained in:
Josef Vlach
2020-05-31 20:32:07 +01:00
parent 35fe376cc3
commit cfeba7e72b
5 changed files with 169 additions and 52 deletions

View File

@@ -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

100
gdscript-comint.el Normal file
View File

@@ -0,0 +1,100 @@
;;; gdscript-comint.el --- Support for comint mode -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2020 GDQuest and contributors
;;
;; Author: Josef Vlach <vlach.josef@gmail.com>
;; 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 <https://www.gnu.org/licenses/>.
;;
;;; 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

View File

@@ -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 - <project-name>*'."
(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] <no options>" (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."

View File

@@ -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 - <project-name>* 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

View File

@@ -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