From 9c0feb18fe5d24873289a7b70c15fe89efbdb931 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Tue, 9 Mar 2021 13:42:35 +0000 Subject: [PATCH] Update the Godot extension to use lsp --- lib/extensions/godot.el | 4 +- lib/extensions/godot/godot-gdscript.el | 3983 ------------------------ lib/extensions/godot/init.el | 10 +- 3 files changed, 4 insertions(+), 3993 deletions(-) delete mode 100644 lib/extensions/godot/godot-gdscript.el diff --git a/lib/extensions/godot.el b/lib/extensions/godot.el index de0d99f..d4a7c6d 100644 --- a/lib/extensions/godot.el +++ b/lib/extensions/godot.el @@ -6,7 +6,7 @@ (require 'extensions/godot/init) ;; Dependencies ---------------------------------- -;; (depends-on 'gdscript-mode) +(depends-on 'gdscript-mode) (defun godot-doc () @@ -20,4 +20,4 @@ :docs "lib/extensions/godot/readme.org") (provide 'extensions/godot) -;;; irc.el ends here +;;; godot.el ends here diff --git a/lib/extensions/godot/godot-gdscript.el b/lib/extensions/godot/godot-gdscript.el deleted file mode 100644 index 6202318..0000000 --- a/lib/extensions/godot/godot-gdscript.el +++ /dev/null @@ -1,3983 +0,0 @@ -;;; godot-gdscript.el --- Major mode for editing Godot Engine GDScript files - -;; Original code Python Mode (from `python.el'): -;; Copyright (C) 2003--2015 Free Software Foundation, Inc. -;; Godot-GDScript Mode: -;; Copyright (C) 2015--2017 Franco Eusébio Garcia - -;; Author: Franco Eusébio Garcia -;; URL: https://github.com/francogarcia/godot-gdscript.el -;; Version: 0.0.1 -;; Keywords: languages - -;;; License: - -;; This file not shipped as 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 is a draft to add support for GDScript in Emacs. GDScript is the -;; language which Godot Game Engine uses to prototype and implement games. Godot -;; is an open-source game engine, available at: . - -;; The mode uses Fabián E. Gallina's `python.el' as the basis and reference for -;; the implementation, due to the similarities between GDScript and Python -;; syntax. However, as some keywords and operators do differ, `python-mode' is -;; not derived; instead, its code is changed to support the GDScript language. - -;; Package-Requires: ((emacs "24.3")) - -;;; Code: - -(require 'ansi-color) -(require 'cl-lib) -(require 'comint) -(require 'json) - -;; Avoid compiler warnings (disable due to package-lint-current-buffer). -;; (defvar view-return-to-alist) -;; (defvar compilation-error-regexp-alist) -;; (defvar outline-heading-end-regexp) -;; (defvar ffap-alist) -;; (defvar electric-indent-inhibit) -;; (autoload 'comint-mode "comint") - -;;;###autoload -(add-to-list 'auto-mode-alist (cons (purecopy "\\.gd\\'") 'godot-gdscript-mode)) -;;;###autoload -(add-to-list 'interpreter-mode-alist (cons (purecopy "godot-gdscript[0-9.]*") 'godot-gdscript-mode)) - -(defgroup godot-gdscript nil - "Godot Engine GDScript Language support for developing games using Emacs." - :group 'languages - :version "24.3" - :link '(emacs-commentary-link "godot-gdscript")) - -;;; Bindings - -(defvar godot-gdscript-mode-map - (let ((map (make-sparse-keymap))) - ;; Movement - (define-key map [remap backward-sentence] 'godot-gdscript-nav-backward-block) - (define-key map [remap forward-sentence] 'godot-gdscript-nav-forward-block) - (define-key map [remap backward-up-list] 'godot-gdscript-nav-backward-up-list) - (define-key map "\C-c\C-j" 'imenu) - ;; Indent specific - (define-key map "\177" 'godot-gdscript-indent-dedent-line-backspace) - (define-key map (kbd "") 'godot-gdscript-indent-dedent-line) - (define-key map "\C-c<" 'godot-gdscript-indent-shift-left) - (define-key map "\C-c>" 'godot-gdscript-indent-shift-right) - ;; Skeletons - (define-key map "\C-c\C-tc" 'godot-gdscript-skeleton-class) - (define-key map "\C-c\C-td" 'godot-gdscript-skeleton-def) - (define-key map "\C-c\C-tf" 'godot-gdscript-skeleton-for) - (define-key map "\C-c\C-ti" 'godot-gdscript-skeleton-if) - (define-key map "\C-c\C-tt" 'godot-gdscript-skeleton-try) - (define-key map "\C-c\C-tw" 'godot-gdscript-skeleton-while) - ;; Shell interaction - (define-key map "\C-c\C-g" 'godot-gdscript-run-godot-editor) - (define-key map "\C-c\C-p" 'godot-gdscript-run-project-in-godot) - (define-key map "\C-c\C-s" 'godot-gdscript-run-current-scene-in-godot) - (define-key map "\C-c\C-e" 'godot-gdscript-edit-current-scene-in-godot) - (define-key map "\C-c\C-r" 'godot-gdscript-run-current-script-in-godot) - (define-key map "\C-c\C-dp" 'godot-gdscript-run-project-in-godot-debug-mode) - (define-key map "\C-c\C-ds" 'godot-gdscript-run-current-scene-in-godot-debug-mode) - (define-key map "\C-c\C-z" 'godot-gdscript-shell-switch-to-shell) - ;; Some util commands - (define-key map "\C-c\C-v" 'godot-gdscript-check) - ;; Utilities - (substitute-key-definition 'complete-symbol 'completion-at-point - map global-map) - (easy-menu-define godot-gdscript-menu map "Godot-Gdscript Mode menu" - `("Godot-Gdscript" - :help "Godot-Gdscript-specific Features" - ["Shift region left" godot-gdscript-indent-shift-left :active mark-active - :help "Shift region left by a single indentation step"] - ["Shift region right" godot-gdscript-indent-shift-right :active mark-active - :help "Shift region right by a single indentation step"] - "-" - ["Start of def/class" beginning-of-defun - :help "Go to start of outermost definition around point"] - ["End of def/class" end-of-defun - :help "Go to end of definition around point"] - ["Mark def/class" mark-defun - :help "Mark outermost definition around point"] - ["Jump to def/class" imenu - :help "Jump to a class or function definition"] - "--" - ("Skeletons") - "---" - ["Switch to shell" godot-gdscript-shell-switch-to-shell - :help "Switch to running inferior Godot-Gdscript process"] - ["Run Godot Engine editor" godot-gdscript-run-godot-editor - :help "Run Godot Editor as a subprocess of Emacs, loading this project into it"] - ["Run project" godot-gdscript-run-project-in-godot - :help "Run the current project in Godot Engine"] - ["Run current scene" godot-gdscript-run-current-scene-in-godot - :help "Run the current scene in Godot Engine"] - ["Edit scene in Godot Engine" godot-gdscript-edit-current-scene-in-godot - :help "Edit the current scene in Godot Engine"] - ["Run current script" godot-gdscript-run-current-script-in-godot - :help "Run the current script in Godot Engine"] - ["Run project (debug mode)" godot-gdscript-run-project-in-godot-debug-mode - :help "Run the project in Godot Engine, using the debug mode option"] - ["Run scene (debug mode)" godot-gdscript-run-current-scene-in-godot-debug-mode - :help "Run the current scene in Godot Engine, using the debug mode option"] - "----" - ["Check file" godot-gdscript-check - :help "Check file for errors"] - ["Complete symbol" completion-at-point - :help "Complete symbol before point"])) - map) - "Keymap for function `godot-gdscript-mode'.") - - - - - -;;; Godot-Gdscript specialized rx - -(eval-when-compile - (defconst godot-gdscript-rx-constituents - `((block-start . ,(rx symbol-start - (or "class" "elif" "else" "except" "finally" "for" - "func" "if" "try" "while" "with" - ;; Multiplayer stuff - "puppet" "master" "remote" "remotesync") - symbol-end)) - (dedenter . ,(rx symbol-start - (or "elif" "else" "except" "finally") - symbol-end)) - (block-ender . ,(rx symbol-start - (or - "break" "continue" "pass" "raise" "return") - symbol-end)) - (decorator . ,(rx line-start - (* space) ?@ (any letter ?_) - (* (any word ?_)))) - (defun . ,(rx symbol-start (or "func" "class") symbol-end)) - (if-name-main . ,(rx line-start "if" (+ space) "__name__" - (+ space) "==" (+ space) - (any ?' ?\") "__main__" (any ?' ?\") - (* space) ?:)) - (symbol-name . ,(rx (any letter ?_) (* (any word ?_)))) - (variable-declaration . ,(rx (or "const" "var"))) - (open-paren . ,(rx (or "{" "[" "("))) - (close-paren . ,(rx (or "}" "]" ")"))) - (simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%))) - ;; FIXME: rx should support (not simple-operator). - (not-simple-operator . ,(rx - (not - (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))) - ;; FIXME: Use regexp-opt. - (operator . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">" - "=" "%" "//" "<<" ">>" "<=" "!" "!=" - "==" ">=" "||" "&&" "is" "not"))) - ;; FIXME: Use regexp-opt. - (assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" - ">>=" "<<=" "&=" "^=" "|="))) - (string-delimiter . ,(rx (and - ;; Match even number of backslashes. - (or (not (any ?\\ ?\' ?\")) point - ;; Quotes might be preceded by a escaped quote. - (and (or (not (any ?\\)) point) ?\\ - (* ?\\ ?\\) (any ?\' ?\"))) - (* ?\\ ?\\) - ;; Match single or triple quotes of any kind. - (group (or "\"" "\"\"\"" "'" "'''"))))) - (coding-cookie . ,(rx line-start ?# (* space) - (or - ;; # coding= - (: "coding" (or ?: ?=) (* space) (group-n 1 (+ (or word ?-)))) - ;; # -*- coding: -*- - (: "-*-" (* space) "coding:" (* space) - (group-n 1 (+ (or word ?-))) (* space) "-*-"))))) - "Additional Godot-Gdscript specific sexps for `godot-gdscript-rx'") - - (defmacro godot-gdscript-rx (&rest regexps) - "Godot-Gdscript mode specialized rx macro. -This variant of `rx' supports common Godot-Gdscript named REGEXPS." - (let ((rx-constituents (append godot-gdscript-rx-constituents rx-constituents))) - (cond ((null regexps) - (error "No regexp")) - ((cdr regexps) - (rx-to-string `(and ,@regexps) t)) - (t - (rx-to-string (car regexps) t)))))) - - -;;; Font-lock and syntax - -(eval-when-compile - (defun godot-gdscript-syntax--context-compiler-macro (form type &optional syntax-ppss) - (pcase type - (`'comment - `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) - (and (nth 4 ppss) (nth 8 ppss)))) - (`'string - `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) - (and (nth 3 ppss) (nth 8 ppss)))) - (`'paren - `(nth 1 (or ,syntax-ppss (syntax-ppss)))) - (_ form)))) - -(defun godot-gdscript-syntax-context (type &optional syntax-ppss) - "Return non-nil if point is on TYPE using SYNTAX-PPSS. -TYPE can be `comment', `string' or `paren'. It returns the start -character address of the specified TYPE." - (declare (compiler-macro godot-gdscript-syntax--context-compiler-macro)) - (let ((ppss (or syntax-ppss (syntax-ppss)))) - (pcase type - (`comment (and (nth 4 ppss) (nth 8 ppss))) - (`string (and (nth 3 ppss) (nth 8 ppss))) - (`paren (nth 1 ppss)) - (_ nil)))) - -(defun godot-gdscript-syntax-context-type (&optional syntax-ppss) - "Return the context type using SYNTAX-PPSS. -The type returned can be `comment', `string' or `paren'." - (let ((ppss (or syntax-ppss (syntax-ppss)))) - (cond - ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string)) - ((nth 1 ppss) 'paren)))) - -(defsubst godot-gdscript-syntax-comment-or-string-p (&optional ppss) - "Return non-nil if PPSS is inside 'comment or 'string." - (nth 8 (or ppss (syntax-ppss)))) - -(defsubst godot-gdscript-syntax-closing-paren-p () - "Return non-nil if char after point is a closing paren." - (= (syntax-class (syntax-after (point))) - (syntax-class (string-to-syntax ")")))) - -(define-obsolete-function-alias - 'godot-gdscript-info-ppss-context #'godot-gdscript-syntax-context "24.3") - -(define-obsolete-function-alias - 'godot-gdscript-info-ppss-context-type #'godot-gdscript-syntax-context-type "24.3") - -(define-obsolete-function-alias - 'godot-gdscript-info-ppss-comment-or-string-p - #'godot-gdscript-syntax-comment-or-string-p "24.3") - -(defvar godot-gdscript-font-lock-keywords - ;; Keywords - `(,(rx symbol-start - (or - "and" "in" "is" "not" "or" - "null" "self" - "String" "bool" "float" "int" - ;; Functions - ;; Variant types - "AABB" "Array" "Basis" "ByteArray" "Color" - "ColorArray" "Dictionary" "Image" "InputEvent" "IntArray" - "Matrix3" "Matrix32" "NodePath" "Object" "Plane" - "Quat" "RID" "RealArray" "Rect2" "StringArray" - "Transform" "Vector2" "Vector2Array" "Vector3" "Vector3Array" - ;; Language keywords - "assert" "break" "breakpoint" "class" "const" "continue" - "default" "do" "elif" "else" "enum" - "export" "extends" "for" "func" "if" "onready" - ;; Multiplayer stuff - "puppet" "master" "remote" "remotesync" - "pass" "preload" "resume" "return" "setget" - "signal" "static" "tool" "var" "while" "yield") - symbol-end) - ;; functions - (,(rx symbol-start "func" (1+ space) (group (1+ (or word ?_)))) - (1 font-lock-function-name-face)) - ;; classes - (,(rx symbol-start "class" (1+ space) (group (1+ (or word ?_)))) - (1 font-lock-type-face)) - ;; Constants - (,(rx symbol-start - (or - "PI" "false" "null" "true") - symbol-end) . font-lock-constant-face) - ;; Decorators. - (,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_)) - (0+ "." (1+ (or word ?_))))) - (1 font-lock-type-face)) - ;; Builtin Exceptions - (,(rx symbol-start - (or - "OK" "FAILED" - "ERR_UNAVAILABLE" "ERR_UNCONFIGURED" "ERR_UNAUTHORIZED" - "ERR_PARAMETER_RANGE_ERROR" "ERR_OUT_OF_MEMORY" "ERR_FILE_NOT_FOUND" - "ERR_FILE_BAD_DRIVE" "ERR_FILE_BAD_PATH" "ERR_FILE_NO_PERMISSION" - "ERR_FILE_ALREADY_IN_USE" "ERR_FILE_CANT_OPEN" "ERR_FILE_CANT_WRITE" - "ERR_FILE_CANT_READ" "ERR_FILE_UNRECOGNIZED" "ERR_FILE_CORRUPT" - "ERR_FILE_MISSING_DEPENDENCIES" "ERR_FILE_EOF" "ERR_CANT_OPEN" - "ERR_CANT_CREATE" "ERROR_QUERY_FAILED" "ERR_ALREADY_IN_USE" - "ERR_LOCKED" "ERR_TIMEOUT" "ERR_CANT_CONNECT" "ERR_CANT_RESOLVE" - "ERR_CONNECTION_ERROR" "ERR_CANT_AQUIRE_RESOURCE" "ERR_CANT_FORK" - "ERR_INVALID_DATA" "ERR_INVALID_PARAMETER" "ERR_ALREADY_EXISTS" - "ERR_DOES_NOT_EXIST" "ERR_DATABASE_CANT_READ" "ERR_DATABASE_CANT_WRITE" - "ERR_COMPILATION_FAILED" "ERR_METHOD_NOT_FOUND" "ERR_LINK_FAILED" - "ERR_SCRIPT_FAILED" "ERR_CYCLIC_LINK" "ERR_INVALID_DECLARATION" - "ERR_DUPLICATE_SYMBOL" "ERR_PARSE_ERROR" "ERR_BUSY" - "ERR_SKIP" "ERR_HELP" "ERR_BUG" "ERR_PRINTER_ON_FIRE" - "ERR_OMFG_THIS_IS_VERY_VERY_BAD" "ERR_WTF") - symbol-end) . font-lock-type-face) - ;; Builtins - (,(rx symbol-start - (or - ;; Inherited methods from Object - "connect" "emit" "get" "set_signal" - ;; Inherited methods from Node - ;; get_node() shorthand - ;; - "$" - ;; () - "_draw" "_enter_tree" "_enter_world" "_exit_tree" "_exit_world" - "_fixed_process" "_init" "_input" "_input" "_physics_process" - "_process" "_process" "_ready" "_unhandled_input" - "_unhandled_input" "_unhandled_key_input" "_unhandled_key_input" - ;; () - "add_child" "add_to_group" "can_process" "duplicate" "find_node" - "get_child" "get_child_count" "get_children" "get_filename" - "get_groups" "get_index" "get_name" "get_node" - "get_node_and_resource" "get_owner" "get_parent" "get_path" - "get_path_to" "get_pause_mode" "get_physics_process_delta_time" - "get_position_in_parent" "get_process_delta_time" - "get_scene_instance_load_placeholder" "get_tree" "get_viewport" - "has_node" "has_node_and_resource" "is_a_parent_of" - "is_displayed_folded" "is_greater_than" "is_in_group" - "is_inside_tree" "is_physics_processing" - "is_physics_processing_internal" "is_processing" - "is_processing_input" "is_processing_internal" - "is_processing_unhandled_input" - "is_processing_unhandled_key_input" - "move_child" "print_stray_nodes" "print_tree" "print_tree_pretty" - "propagate_call" "propagate_notification" "queue_free" "raise" - "remove_and_skip" "remove_child" "remove_from_group" "replace_by" - "request_ready" "set_display_folded" "set_filename" "set_name" - "set_owner" "set_pause_mode" "set_physics_process" - "set_physics_process_internal" "set_process" "set_process_input" - "set_process_internal" "set_process_unhandled_input" - "set_process_unhandled_key_input" - "set_scene_instance_load_placeholder" - ;; Missing functions from header - "basefunc" "call" "new" "instance" - ;; Exported functions - ;; () - "Color8" "ColorN" "abs" "acos" "asin" "atan" "atan2" "bytes2var" - "cartesian2polar" "ceil" "char" "clamp" "convert" "cos" "cosh" - "db2linear" "decimals" "dectime" "deg2rad" "dict2inst" "ease" - "exp" "floor" "fmod" "fposmod" "funcref" "hash" "inst2dict" - "instance_from_id" "inverse_lerp" "is_inf" "is_instance_valid" - "is_nan" "len" "lerp" "linear2db" "load" "log" "max" "min" - "nearest_po2" "parse_json" "polar2cartesian" "pow" "print" - "print_stack" "printerr" "printraw" "prints" "printt" "rad2deg" - "rand_range" "rand_seed" "randf" "randi" "randomize" "range" - "range_lerp" "round" "seed" "sign" "sinh" "sqrt" "stepify" "str" - "str2var" "tan" "tanh" "to_json" "type_exists" "typeof" - "validate_json" "var2bytes" "var2str" "weakref" "wrapf" "wrapi" - "sin") - symbol-end) . font-lock-builtin-face) - ;; assignments - ;; support for a = b = c = 5 - (,(lambda (limit) - (let ((re (godot-gdscript-rx (group (+ (any word ?. ?_))) - (? ?\[ (+ (not (any ?\]))) ?\]) (* space) - assignment-operator)) - (res nil)) - (while (and (setq res (re-search-forward re limit t)) - (or (godot-gdscript-syntax-context 'paren) - (equal (char-after (point-marker)) ?=)))) - res)) - (1 font-lock-variable-name-face nil nil)) - ;; support for a, b, c = (1, 2, 3) - (,(lambda (limit) - (let ((re (godot-gdscript-rx (group (+ (any word ?. ?_))) (* space) - (* ?, (* space) (+ (any word ?. ?_)) (* space)) - ?, (* space) (+ (any word ?. ?_)) (* space) - assignment-operator)) - (res nil)) - (while (and (setq res (re-search-forward re limit t)) - (goto-char (match-end 1)) - (godot-gdscript-syntax-context 'paren))) - res)) - (1 font-lock-variable-name-face nil nil)))) - -(defconst godot-gdscript-syntax-propertize-function - (syntax-propertize-rules - ((godot-gdscript-rx string-delimiter) - (0 (ignore (godot-gdscript-syntax-stringify)))))) - -(defsubst godot-gdscript-syntax-count-quotes (quote-char &optional point limit) - "Count number of quotes around point (max is 3). -QUOTE-CHAR is the quote char to count. Optional argument POINT is -the point where scan starts (defaults to current point), and -LIMIT is used to limit the scan." - (let ((i 0)) - (while (and (< i 3) - (or (not limit) (< (+ point i) limit)) - (eq (char-after (+ point i)) quote-char)) - (setq i (1+ i))) - i)) - -(defun godot-gdscript-syntax-stringify () - "Put `syntax-table' property correctly on single/triple quotes." - (let* ((num-quotes (length (match-string-no-properties 1))) - (ppss (prog2 - (backward-char num-quotes) - (syntax-ppss) - (forward-char num-quotes))) - (string-start (and (not (nth 4 ppss)) (nth 8 ppss))) - (quote-starting-pos (- (point) num-quotes)) - (quote-ending-pos (point)) - (num-closing-quotes - (and string-start - (godot-gdscript-syntax-count-quotes - (char-before) string-start quote-starting-pos)))) - (cond ((and string-start (= num-closing-quotes 0)) - ;; This set of quotes doesn't match the string starting - ;; kind. Do nothing. - nil) - ((not string-start) - ;; This set of quotes delimit the start of a string. - (put-text-property quote-starting-pos (1+ quote-starting-pos) - 'syntax-table (string-to-syntax "|"))) - ((= num-quotes num-closing-quotes) - ;; This set of quotes delimit the end of a string. - (put-text-property (1- quote-ending-pos) quote-ending-pos - 'syntax-table (string-to-syntax "|"))) - ((> num-quotes num-closing-quotes) - ;; This may only happen whenever a triple quote is closing - ;; a single quoted string. Add string delimiter syntax to - ;; all three quotes. - (put-text-property quote-starting-pos quote-ending-pos - 'syntax-table (string-to-syntax "|")))))) - -(defvar godot-gdscript-mode-syntax-table - (let ((table (make-syntax-table))) - ;; Give punctuation syntax to ASCII that normally has symbol - ;; syntax or has word syntax and isn't a letter. - (let ((symbol (string-to-syntax "_")) - (sst (standard-syntax-table))) - (dotimes (i 128) - (unless (= i ?_) - (if (equal symbol (aref sst i)) - (modify-syntax-entry i "." table))))) - (modify-syntax-entry ?$ "." table) - (modify-syntax-entry ?% "." table) - ;; exceptions - (modify-syntax-entry ?# "<" table) - (modify-syntax-entry ?\n ">" table) - (modify-syntax-entry ?' "\"" table) - (modify-syntax-entry ?` "$" table) - table) - "Syntax table for Godot-Gdscript files.") - -(defvar godot-gdscript-dotty-syntax-table - (let ((table (make-syntax-table godot-gdscript-mode-syntax-table))) - (modify-syntax-entry ?. "w" table) - (modify-syntax-entry ?_ "w" table) - table) - "Dotty syntax table for Godot-Gdscript files. -It makes underscores and dots word constituent chars.") - -;;; Indentation - -(defcustom godot-gdscript-indent-offset 4 - "Default indentation offset for Godot-Gdscript." - :group 'godot-gdscript - :type 'integer - :safe 'integerp) - -(defcustom godot-gdscript-indent-guess-indent-offset t - "Non-nil tells Godot-Gdscript mode to guess `godot-gdscript-indent-offset' value." - :type 'boolean - :group 'godot-gdscript - :safe 'booleanp) - -(defcustom godot-gdscript-indent-trigger-commands - '(indent-for-tab-command yas-expand yas/expand) - "Commands that might trigger a `godot-gdscript-indent-line' call." - :type '(repeat symbol) - :group 'godot-gdscript) - -(define-obsolete-variable-alias - 'godot-gdscript-indent 'godot-gdscript-indent-offset "24.3") - -(define-obsolete-variable-alias - 'godot-gdscript-guess-indent 'godot-gdscript-indent-guess-indent-offset "24.3") - -(defvar godot-gdscript-indent-current-level 0 - "Deprecated var available for compatibility.") - -(defvar godot-gdscript-indent-levels '(0) - "Deprecated var available for compatibility.") - -(make-obsolete-variable - 'godot-gdscript-indent-current-level - "The indentation API changed to avoid global state. -The function `godot-gdscript-indent-calculate-levels' does not use it -anymore. If you were defadvising it and or depended on this -variable for indentation customizations, refactor your code to -work on `godot-gdscript-indent-calculate-indentation' instead." - "24.5") - -(make-obsolete-variable - 'godot-gdscript-indent-levels - "The indentation API changed to avoid global state. -The function `godot-gdscript-indent-calculate-levels' does not use it -anymore. If you were defadvising it and or depended on this -variable for indentation customizations, refactor your code to -work on `godot-gdscript-indent-calculate-indentation' instead." - "24.5") - -(defun godot-gdscript-indent-guess-indent-offset () - "Guess and set `godot-gdscript-indent-offset' for the current buffer." - (interactive) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (let ((block-end)) - (while (and (not block-end) - (re-search-forward - (godot-gdscript-rx line-start block-start) nil t)) - (when (and - (not (godot-gdscript-syntax-context-type)) - (progn - (goto-char (line-end-position)) - (godot-gdscript-util-forward-comment -1) - (if (equal (char-before) ?:) - t - (forward-line 1) - (when (godot-gdscript-info-block-continuation-line-p) - (while (and (godot-gdscript-info-continuation-line-p) - (not (eobp))) - (forward-line 1)) - (godot-gdscript-util-forward-comment -1) - (when (equal (char-before) ?:) - t))))) - (setq block-end (point-marker)))) - (let ((indentation - (when block-end - (goto-char block-end) - (godot-gdscript-util-forward-comment) - (current-indentation)))) - (if (and indentation (not (zerop indentation))) - (set (make-local-variable 'godot-gdscript-indent-offset) indentation) - (message "Can't guess godot-gdscript-indent-offset, using defaults: %s" - godot-gdscript-indent-offset))))))) - -(defun godot-gdscript-indent-context () - "Get information about the current indentation context. -Context is returned in a cons with the form (STATUS . START). - -STATUS can be one of the following: - -keyword -------- - -:after-comment - - Point is after a comment line. - - START is the position of the \"#\" character. -:inside-string - - Point is inside string. - - START is the position of the first quote that starts it. -:no-indent - - No possible indentation case matches. - - START is always zero. - -:inside-paren - - Fallback case when point is inside paren. - - START is the first non space char position *after* the open paren. -:inside-paren-at-closing-nested-paren - - Point is on a line that contains a nested paren closer. - - START is the position of the open paren it closes. -:inside-paren-at-closing-paren - - Point is on a line that contains a paren closer. - - START is the position of the open paren. -:inside-paren-newline-start - - Point is inside a paren with items starting in their own line. - - START is the position of the open paren. -:inside-paren-newline-start-from-block - - Point is inside a paren with items starting in their own line - from a block start. - - START is the position of the open paren. - -:after-backslash - - Fallback case when point is after backslash. - - START is the char after the position of the backslash. -:after-backslash-assignment-continuation - - Point is after a backslashed assignment. - - START is the char after the position of the backslash. -:after-backslash-block-continuation - - Point is after a backslashed block continuation. - - START is the char after the position of the backslash. -:after-backslash-dotted-continuation - - Point is after a backslashed dotted continuation. Previous - line must contain a dot to align with. - - START is the char after the position of the backslash. -:after-backslash-first-line - - First line following a backslashed continuation. - - START is the char after the position of the backslash. - -:after-block-end - - Point is after a line containing a block ender. - - START is the position where the ender starts. -:after-block-start - - Point is after a line starting a block. - - START is the position where the block starts. -:after-line - - Point is after a simple line. - - START is the position where the previous line starts. -:at-dedenter-block-start - - Point is on a line starting a dedenter block. - - START is the position where the dedenter block starts." - (save-restriction - (widen) - (let ((ppss (save-excursion - (beginning-of-line) - (syntax-ppss)))) - (cond - ;; Beginning of buffer. - ((= (line-number-at-pos) 1) - (cons :no-indent 0)) - ;; Inside a string. - ((let ((start (godot-gdscript-syntax-context 'string ppss))) - (when start - (cons :inside-string start)))) - ;; Inside a paren. - ((let* ((start (godot-gdscript-syntax-context 'paren ppss)) - (starts-in-newline - (when start - (save-excursion - (goto-char start) - (forward-char) - (not - (= (line-number-at-pos) - (progn - (godot-gdscript-util-forward-comment) - (line-number-at-pos)))))))) - (when start - (cond - ;; Current line only holds the closing paren. - ((save-excursion - (skip-syntax-forward " ") - (when (and (godot-gdscript-syntax-closing-paren-p) - (progn - (forward-char 1) - (not (godot-gdscript-syntax-context 'paren)))) - (cons :inside-paren-at-closing-paren start)))) - ;; Current line only holds a closing paren for nested. - ((save-excursion - (back-to-indentation) - (godot-gdscript-syntax-closing-paren-p)) - (cons :inside-paren-at-closing-nested-paren start)) - ;; This line starts from a opening block in its own line. - ((save-excursion - (goto-char start) - (when (and - starts-in-newline - (save-excursion - (back-to-indentation) - (looking-at (godot-gdscript-rx block-start)))) - (cons - :inside-paren-newline-start-from-block start)))) - (starts-in-newline - (cons :inside-paren-newline-start start)) - ;; General case. - (t (cons :inside-paren - (save-excursion - (goto-char (1+ start)) - (skip-syntax-forward "(" 1) - (skip-syntax-forward " ") - (point)))))))) - ;; After backslash. - ((let ((start (when (not (godot-gdscript-syntax-comment-or-string-p ppss)) - (godot-gdscript-info-line-ends-backslash-p - (1- (line-number-at-pos)))))) - (when start - (cond - ;; Continuation of dotted expression. - ((save-excursion - (back-to-indentation) - (when (eq (char-after) ?\.) - ;; Move point back until it's not inside a paren. - (while (prog2 - (forward-line -1) - (and (not (bobp)) - (godot-gdscript-syntax-context 'paren)))) - (goto-char (line-end-position)) - (while (and (search-backward - "." (line-beginning-position) t) - (godot-gdscript-syntax-context-type))) - ;; Ensure previous statement has dot to align with. - (when (and (eq (char-after) ?\.) - (not (godot-gdscript-syntax-context-type))) - (cons :after-backslash-dotted-continuation (point)))))) - ;; Continuation of block definition. - ((let ((block-continuation-start - (godot-gdscript-info-block-continuation-line-p))) - (when block-continuation-start - (save-excursion - (goto-char block-continuation-start) - (re-search-forward - (godot-gdscript-rx block-start (* space)) - (line-end-position) t) - (cons :after-backslash-block-continuation (point)))))) - ;; Continuation of assignment. - ((let ((assignment-continuation-start - (godot-gdscript-info-assignment-continuation-line-p))) - (when assignment-continuation-start - (save-excursion - (goto-char assignment-continuation-start) - (cons :after-backslash-assignment-continuation (point)))))) - ;; First line after backslash continuation start. - ((save-excursion - (goto-char start) - (when (or (= (line-number-at-pos) 1) - (not (godot-gdscript-info-beginning-of-backslash - (1- (line-number-at-pos))))) - (cons :after-backslash-first-line start)))) - ;; General case. - (t (cons :after-backslash start)))))) - ;; After beginning of block. - ((let ((start (save-excursion - (back-to-indentation) - (godot-gdscript-util-forward-comment -1) - (when (equal (char-before) ?:) - (godot-gdscript-nav-beginning-of-block))))) - (when start - (cons :after-block-start start)))) - ;; At dedenter statement. - ((let ((start (godot-gdscript-info-dedenter-statement-p))) - (when start - (cons :at-dedenter-block-start start)))) - ;; After normal line, comment or ender (default case). - ((save-excursion - (back-to-indentation) - (skip-chars-backward " \t\n") - (godot-gdscript-nav-beginning-of-statement) - (cons - (cond ((godot-gdscript-info-current-line-comment-p) - :after-comment) - ((save-excursion - (goto-char (line-end-position)) - (godot-gdscript-util-forward-comment -1) - (godot-gdscript-nav-beginning-of-statement) - (looking-at (godot-gdscript-rx block-ender))) - :after-block-end) - (t :after-line)) - (point)))))))) - -(defun godot-gdscript-indent--calculate-indentation () - "Internal implementation of `godot-gdscript-indent-calculate-indentation'. -May return an integer for the maximum possible indentation at -current context or a list of integers. The latter case is only -happening for :at-dedenter-block-start context since the -possibilities can be narrowed to specific indentation points." - (save-restriction - (widen) - (save-excursion - (pcase (godot-gdscript-indent-context) - (`(:no-indent . ,_) 0) - (`(,(or :after-line - :after-comment - :inside-string - :after-backslash - :inside-paren-at-closing-paren - :inside-paren-at-closing-nested-paren) . ,start) - ;; Copy previous indentation. - (goto-char start) - (current-indentation)) - (`(,(or :after-block-start - :after-backslash-first-line - :inside-paren-newline-start) . ,start) - ;; Add one indentation level. - (goto-char start) - (+ (current-indentation) godot-gdscript-indent-offset)) - (`(,(or :inside-paren - :after-backslash-block-continuation - :after-backslash-assignment-continuation - :after-backslash-dotted-continuation) . ,start) - ;; Use the column given by the context. - (goto-char start) - (current-column)) - (`(:after-block-end . ,start) - ;; Subtract one indentation level. - (goto-char start) - (- (current-indentation) godot-gdscript-indent-offset)) - (`(:at-dedenter-block-start . ,_) - ;; List all possible indentation levels from opening blocks. - (let ((opening-block-start-points - (godot-gdscript-info-dedenter-opening-block-positions))) - (if (not opening-block-start-points) - 0 ; if not found default to first column - (mapcar (lambda (pos) - (save-excursion - (goto-char pos) - (current-indentation))) - opening-block-start-points)))) - (`(,(or :inside-paren-newline-start-from-block) . ,start) - ;; Add two indentation levels to make the suite stand out. - (goto-char start) - (+ (current-indentation) (* godot-gdscript-indent-offset 2))))))) - -(defun godot-gdscript-indent--calculate-levels (indentation) - "Calculate levels list given INDENTATION. -Argument INDENTATION can either be an integer or a list of -integers. Levels are returned in ascending order, and in the -case INDENTATION is a list, this order is enforced." - (if (listp indentation) - (sort (copy-sequence indentation) #'<) - (let* ((remainder (% indentation godot-gdscript-indent-offset)) - (steps (/ (- indentation remainder) godot-gdscript-indent-offset)) - (levels (mapcar (lambda (step) - (* godot-gdscript-indent-offset step)) - (number-sequence steps 0 -1)))) - (reverse - (if (not (zerop remainder)) - (cons indentation levels) - levels))))) - -(defun godot-gdscript-indent--previous-level (levels indentation) - "Return previous level from LEVELS relative to INDENTATION." - (let* ((levels (sort (copy-sequence levels) #'>)) - (default (car levels))) - (catch 'return - (dolist (level levels) - (when (funcall #'< level indentation) - (throw 'return level))) - default))) - -(defun godot-gdscript-indent-calculate-indentation (&optional previous) - "Calculate indentation. -Get indentation of PREVIOUS level when argument is non-nil. -Return the max level of the cycle when indentation reaches the -minimum." - (let* ((indentation (godot-gdscript-indent--calculate-indentation)) - (levels (godot-gdscript-indent--calculate-levels indentation))) - (if previous - (godot-gdscript-indent--previous-level levels (current-indentation)) - (apply #'max levels)))) - -(defun godot-gdscript-indent-line (&optional previous) - "Internal implementation of `godot-gdscript-indent-line-function'. -Use the PREVIOUS level when argument is non-nil, otherwise indent -to the maximum available level. When indentation is the minimum -possible and PREVIOUS is non-nil, cycle back to the maximum -level." - (let ((follow-indentation-p - ;; Check if point is within indentation. - (and (<= (line-beginning-position) (point)) - (>= (+ (line-beginning-position) - (current-indentation)) - (point))))) - (save-excursion - (indent-line-to - (godot-gdscript-indent-calculate-indentation previous)) - (godot-gdscript-info-dedenter-opening-block-message)) - (when follow-indentation-p - (back-to-indentation)))) - -(defun godot-gdscript-indent-calculate-levels () - "Return possible indentation levels." - (godot-gdscript-indent--calculate-levels - (godot-gdscript-indent--calculate-indentation))) - -(defun godot-gdscript-indent-line-function () - "`indent-line-function' for Godot-Gdscript mode. -When the variable `last-command' is equal to one of the symbols -inside `godot-gdscript-indent-trigger-commands' it cycles possible -indentation levels from right to left." - (godot-gdscript-indent-line - (and (memq this-command godot-gdscript-indent-trigger-commands) - (eq last-command this-command)))) - -(defun godot-gdscript-indent-dedent-line () - "De-indent current line." - (interactive "*") - (when (and (not (bolp)) - (not (godot-gdscript-syntax-comment-or-string-p)) - (= (current-indentation) (current-column))) - (godot-gdscript-indent-line t) - t)) - -(defun godot-gdscript-indent-dedent-line-backspace (arg) - "De-indent current line. -Argument ARG is passed to `backward-delete-char-untabify' when -point is not in between the indentation." - (interactive "*p") - (unless (godot-gdscript-indent-dedent-line) - (backward-delete-char-untabify arg))) - -(put 'godot-gdscript-indent-dedent-line-backspace 'delete-selection 'supersede) - -(defun godot-gdscript-indent-region (start end) - "Indent a Godot-Gdscript region automagically. - -Called from a program, START and END specify the region to indent." - (let ((deactivate-mark nil)) - (save-excursion - (goto-char end) - (setq end (point-marker)) - (goto-char start) - (or (bolp) (forward-line 1)) - (while (< (point) end) - (or (and (bolp) (eolp)) - (when (and - ;; Skip if previous line is empty or a comment. - (save-excursion - (let ((line-is-comment-p - (godot-gdscript-info-current-line-comment-p))) - (forward-line -1) - (not - (or (and (godot-gdscript-info-current-line-comment-p) - ;; Unless this line is a comment too. - (not line-is-comment-p)) - (godot-gdscript-info-current-line-empty-p))))) - ;; Don't mess with strings, unless it's the - ;; enclosing set of quotes. - (or (not (godot-gdscript-syntax-context 'string)) - (eq - (syntax-after - (+ (1- (point)) - (current-indentation) - (godot-gdscript-syntax-count-quotes (char-after) (point)))) - (string-to-syntax "|"))) - ;; Skip if current line is a block start, a - ;; dedenter or block ender. - (save-excursion - (back-to-indentation) - (not (looking-at - (godot-gdscript-rx - (or block-start dedenter block-ender)))))) - (godot-gdscript-indent-line))) - (forward-line 1)) - (move-marker end nil)))) - -(defun godot-gdscript-indent-shift-left (start end &optional count) - "Shift lines contained in region START END by COUNT columns to the left. -COUNT defaults to `godot-gdscript-indent-offset'. If region isn't -active, the current line is shifted. The shifted region includes -the lines in which START and END lie. An error is signaled if -any lines in the region are indented less than COUNT columns." - (interactive - (if mark-active - (list (region-beginning) (region-end) current-prefix-arg) - (list (line-beginning-position) (line-end-position) current-prefix-arg))) - (if count - (setq count (prefix-numeric-value count)) - (setq count godot-gdscript-indent-offset)) - (when (> count 0) - (let ((deactivate-mark nil)) - (save-excursion - (goto-char start) - (while (< (point) end) - (if (and (< (current-indentation) count) - (not (looking-at "[ \t]*$"))) - (error "Can't shift all lines enough")) - (forward-line)) - (indent-rigidly start end (- count)))))) - -(add-to-list 'debug-ignored-errors "^Can't shift all lines enough") - -(defun godot-gdscript-indent-shift-right (start end &optional count) - "Shift lines contained in region START END by COUNT columns to the right. -COUNT defaults to `godot-gdscript-indent-offset'. If region isn't -active, the current line is shifted. The shifted region includes -the lines in which START and END lie." - (interactive - (if mark-active - (list (region-beginning) (region-end) current-prefix-arg) - (list (line-beginning-position) (line-end-position) current-prefix-arg))) - (let ((deactivate-mark nil)) - (setq count (if count (prefix-numeric-value count) - godot-gdscript-indent-offset)) - (indent-rigidly start end count))) - -(defun godot-gdscript-indent-post-self-insert-function () - "Adjust indentation after insertion of some characters. -This function is intended to be added to `post-self-insert-hook.' -If a line renders a paren alone, after adding a char before it, -the line will be re-indented automatically if needed." - (when (and electric-indent-mode - (eq (char-before) last-command-event)) - (cond - ;; Electric indent inside parens - ((and - (not (bolp)) - (let ((paren-start (godot-gdscript-syntax-context 'paren))) - ;; Check that point is inside parens. - (when paren-start - (not - ;; Filter the case where input is happening in the same - ;; line where the open paren is. - (= (line-number-at-pos) - (line-number-at-pos paren-start))))) - ;; When content has been added before the closing paren or a - ;; comma has been inserted, it's ok to do the trick. - (or - (memq (char-after) '(?\) ?\] ?\})) - (eq (char-before) ?,))) - (save-excursion - (goto-char (line-beginning-position)) - (let ((indentation (godot-gdscript-indent-calculate-indentation))) - (when (< (current-indentation) indentation) - (indent-line-to indentation))))) - ;; Electric colon - ((and (eq ?: last-command-event) - (memq ?: electric-indent-chars) - (not current-prefix-arg) - ;; Trigger electric colon only at end of line - (eolp) - ;; Avoid re-indenting on extra colon - (not (equal ?: (char-before (1- (point))))) - (not (godot-gdscript-syntax-comment-or-string-p))) - ;; Just re-indent dedenters - (let ((dedenter-pos (godot-gdscript-info-dedenter-statement-p)) - (current-pos (point))) - (when dedenter-pos - (save-excursion - (goto-char dedenter-pos) - (godot-gdscript-indent-line) - (unless (= (line-number-at-pos dedenter-pos) - (line-number-at-pos current-pos)) - ;; Reindent region if this is a multiline statement - (godot-gdscript-indent-region dedenter-pos current-pos))))))))) - -;;; Navigation - -(defvar godot-gdscript-nav-beginning-of-defun-regexp - (godot-gdscript-rx line-start (* space) defun (+ space) (group symbol-name)) - "Regexp matching class or function definition. -The name of the defun should be grouped so it can be retrieved -via `match-string'.") - -(defun godot-gdscript-nav--beginning-of-defun (&optional arg) - "Internal implementation of `godot-gdscript-nav-beginning-of-defun'. -With positive ARG search backwards, else search forwards." - (when (or (null arg) (= arg 0)) (setq arg 1)) - (let* ((re-search-fn (if (> arg 0) - #'re-search-backward - #'re-search-forward)) - (line-beg-pos (line-beginning-position)) - (line-content-start (+ line-beg-pos (current-indentation))) - (pos (point-marker)) - (beg-indentation - (and (> arg 0) - (save-excursion - (while (and - (not (godot-gdscript-info-looking-at-beginning-of-defun)) - (godot-gdscript-nav-backward-block))) - (or (and (godot-gdscript-info-looking-at-beginning-of-defun) - (+ (current-indentation) godot-gdscript-indent-offset)) - 0)))) - (found - (progn - (when (and (< arg 0) - (godot-gdscript-info-looking-at-beginning-of-defun)) - (end-of-line 1)) - (while (and (funcall re-search-fn - godot-gdscript-nav-beginning-of-defun-regexp nil t) - (or (godot-gdscript-syntax-context-type) - ;; Handle nested defuns when moving - ;; backwards by checking indentation. - (and (> arg 0) - (not (= (current-indentation) 0)) - (>= (current-indentation) beg-indentation))))) - (and (godot-gdscript-info-looking-at-beginning-of-defun) - (or (not (= (line-number-at-pos pos) - (line-number-at-pos))) - (and (>= (point) line-beg-pos) - (<= (point) line-content-start) - (> pos line-content-start))))))) - (if found - (or (beginning-of-line 1) t) - (and (goto-char pos) nil)))) - -(defun godot-gdscript-nav-beginning-of-defun (&optional arg) - "Move point to `beginning-of-defun'. -With positive ARG search backwards else search forward. -ARG nil or 0 defaults to 1. When searching backwards, -nested defuns are handled with care depending on current -point position. Return non-nil if point is moved to -`beginning-of-defun'." - (when (or (null arg) (= arg 0)) (setq arg 1)) - (let ((found)) - (while (and (not (= arg 0)) - (let ((keep-searching-p - (godot-gdscript-nav--beginning-of-defun arg))) - (when (and keep-searching-p (null found)) - (setq found t)) - keep-searching-p)) - (setq arg (if (> arg 0) (1- arg) (1+ arg)))) - found)) - -(defun godot-gdscript-nav-end-of-defun () - "Move point to the end of def or class. -Returns nil if point is not in a def or class." - (interactive) - (let ((beg-defun-indent) - (beg-pos (point))) - (when (or (godot-gdscript-info-looking-at-beginning-of-defun) - (godot-gdscript-nav-beginning-of-defun 1) - (godot-gdscript-nav-beginning-of-defun -1)) - (setq beg-defun-indent (current-indentation)) - (while (progn - (godot-gdscript-nav-end-of-statement) - (godot-gdscript-util-forward-comment 1) - (and (> (current-indentation) beg-defun-indent) - (not (eobp))))) - (godot-gdscript-util-forward-comment -1) - (forward-line 1) - ;; Ensure point moves forward. - (and (> beg-pos (point)) (goto-char beg-pos))))) - -(defun godot-gdscript-nav--syntactically (fn poscompfn &optional contextfn) - "Move point using FN avoiding places with specific context. -FN must take no arguments. POSCOMPFN is a two arguments function -used to compare current and previous point after it is moved -using FN, this is normally a less-than or greater-than -comparison. Optional argument CONTEXTFN defaults to -`godot-gdscript-syntax-context-type' and is used for checking current -point context, it must return a non-nil value if this point must -be skipped." - (let ((contextfn (or contextfn 'godot-gdscript-syntax-context-type)) - (start-pos (point-marker)) - (prev-pos)) - (catch 'found - (while t - (let* ((newpos - (and (funcall fn) (point-marker))) - (context (funcall contextfn))) - (cond ((and (not context) newpos - (or (and (not prev-pos) newpos) - (and prev-pos newpos - (funcall poscompfn newpos prev-pos)))) - (throw 'found (point-marker))) - ((and newpos context) - (setq prev-pos (point))) - (t (when (not newpos) (goto-char start-pos)) - (throw 'found nil)))))))) - -(defun godot-gdscript-nav--forward-defun (arg) - "Internal implementation of godot-gdscript-nav-{backward,forward}-defun. -Uses ARG to define which function to call, and how many times -repeat it." - (let ((found)) - (while (and (> arg 0) - (setq found - (godot-gdscript-nav--syntactically - (lambda () - (re-search-forward - godot-gdscript-nav-beginning-of-defun-regexp nil t)) - '>))) - (setq arg (1- arg))) - (while (and (< arg 0) - (setq found - (godot-gdscript-nav--syntactically - (lambda () - (re-search-backward - godot-gdscript-nav-beginning-of-defun-regexp nil t)) - '<))) - (setq arg (1+ arg))) - found)) - -(defun godot-gdscript-nav-backward-defun (&optional arg) - "Navigate to closer defun backward ARG times. -Unlikely `godot-gdscript-nav-beginning-of-defun' this doesn't care about -nested definitions." - (interactive "^p") - (godot-gdscript-nav--forward-defun (- (or arg 1)))) - -(defun godot-gdscript-nav-forward-defun (&optional arg) - "Navigate to closer defun forward ARG times. -Unlikely `godot-gdscript-nav-beginning-of-defun' this doesn't care about -nested definitions." - (interactive "^p") - (godot-gdscript-nav--forward-defun (or arg 1))) - -(defun godot-gdscript-nav-beginning-of-statement () - "Move to start of current statement." - (interactive "^") - (back-to-indentation) - (let* ((ppss (syntax-ppss)) - (context-point - (or - (godot-gdscript-syntax-context 'paren ppss) - (godot-gdscript-syntax-context 'string ppss)))) - (cond ((bobp)) - (context-point - (goto-char context-point) - (godot-gdscript-nav-beginning-of-statement)) - ((save-excursion - (forward-line -1) - (godot-gdscript-info-line-ends-backslash-p)) - (forward-line -1) - (godot-gdscript-nav-beginning-of-statement)))) - (point-marker)) - -(defun godot-gdscript-nav-end-of-statement (&optional noend) - "Move to end of current statement. -Optional argument NOEND is internal and makes the logic to not -jump to the end of line when moving forward searching for the end -of the statement." - (interactive "^") - (let (string-start bs-pos) - (while (and (or noend (goto-char (line-end-position))) - (not (eobp)) - (cond ((setq string-start (godot-gdscript-syntax-context 'string)) - (goto-char string-start) - (if (godot-gdscript-syntax-context 'paren) - ;; Ended up inside a paren, roll again. - (godot-gdscript-nav-end-of-statement t) - ;; This is not inside a paren, move to the - ;; end of this string. - (goto-char (+ (point) - (godot-gdscript-syntax-count-quotes - (char-after (point)) (point)))) - (or (re-search-forward (rx (syntax string-delimiter)) nil t) - (goto-char (point-max))))) - ((godot-gdscript-syntax-context 'paren) - ;; The statement won't end before we've escaped - ;; at least one level of parenthesis. - (condition-case err - (goto-char (scan-lists (point) 1 -1)) - (scan-error (goto-char (nth 3 err))))) - ((setq bs-pos (godot-gdscript-info-line-ends-backslash-p)) - (goto-char bs-pos) - (forward-line 1)))))) - (point-marker)) - -(defun godot-gdscript-nav-backward-statement (&optional arg) - "Move backward to previous statement. -With ARG, repeat. See `godot-gdscript-nav-forward-statement'." - (interactive "^p") - (or arg (setq arg 1)) - (godot-gdscript-nav-forward-statement (- arg))) - -(defun godot-gdscript-nav-forward-statement (&optional arg) - "Move forward to next statement. -With ARG, repeat. With negative argument, move ARG times -backward to previous statement." - (interactive "^p") - (or arg (setq arg 1)) - (while (> arg 0) - (godot-gdscript-nav-end-of-statement) - (godot-gdscript-util-forward-comment) - (godot-gdscript-nav-beginning-of-statement) - (setq arg (1- arg))) - (while (< arg 0) - (godot-gdscript-nav-beginning-of-statement) - (godot-gdscript-util-forward-comment -1) - (godot-gdscript-nav-beginning-of-statement) - (setq arg (1+ arg)))) - -(defun godot-gdscript-nav-beginning-of-block () - "Move to start of current block." - (interactive "^") - (let ((starting-pos (point))) - (if (progn - (godot-gdscript-nav-beginning-of-statement) - (looking-at (godot-gdscript-rx block-start))) - (point-marker) - ;; Go to first line beginning a statement - (while (and (not (bobp)) - (or (and (godot-gdscript-nav-beginning-of-statement) nil) - (godot-gdscript-info-current-line-comment-p) - (godot-gdscript-info-current-line-empty-p))) - (forward-line -1)) - (let ((block-matching-indent - (- (current-indentation) godot-gdscript-indent-offset))) - (while - (and (godot-gdscript-nav-backward-block) - (> (current-indentation) block-matching-indent))) - (if (and (looking-at (godot-gdscript-rx block-start)) - (= (current-indentation) block-matching-indent)) - (point-marker) - (and (goto-char starting-pos) nil)))))) - -(defun godot-gdscript-nav-end-of-block () - "Move to end of current block." - (interactive "^") - (when (godot-gdscript-nav-beginning-of-block) - (let ((block-indentation (current-indentation))) - (godot-gdscript-nav-end-of-statement) - (while (and (forward-line 1) - (not (eobp)) - (or (and (> (current-indentation) block-indentation) - (or (godot-gdscript-nav-end-of-statement) t)) - (godot-gdscript-info-current-line-comment-p) - (godot-gdscript-info-current-line-empty-p)))) - (godot-gdscript-util-forward-comment -1) - (point-marker)))) - -(defun godot-gdscript-nav-backward-block (&optional arg) - "Move backward to previous block of code. -With ARG, repeat. See `godot-gdscript-nav-forward-block'." - (interactive "^p") - (or arg (setq arg 1)) - (godot-gdscript-nav-forward-block (- arg))) - -(defun godot-gdscript-nav-forward-block (&optional arg) - "Move forward to next block of code. -With ARG, repeat. With negative argument, move ARG times -backward to previous block." - (interactive "^p") - (or arg (setq arg 1)) - (let ((block-start-regexp - (godot-gdscript-rx line-start (* whitespace) block-start)) - (starting-pos (point))) - (while (> arg 0) - (godot-gdscript-nav-end-of-statement) - (while (and - (re-search-forward block-start-regexp nil t) - (godot-gdscript-syntax-context-type))) - (setq arg (1- arg))) - (while (< arg 0) - (godot-gdscript-nav-beginning-of-statement) - (while (and - (re-search-backward block-start-regexp nil t) - (godot-gdscript-syntax-context-type))) - (setq arg (1+ arg))) - (godot-gdscript-nav-beginning-of-statement) - (if (not (looking-at (godot-gdscript-rx block-start))) - (and (goto-char starting-pos) nil) - (and (not (= (point) starting-pos)) (point-marker))))) - -(defun godot-gdscript-nav--lisp-forward-sexp (&optional arg) - "Standard version `forward-sexp'. -It ignores completely the value of `forward-sexp-function' by -setting it to nil before calling `forward-sexp'. With positive -ARG move forward only one sexp, else move backwards." - (let ((forward-sexp-function) - (arg (if (or (not arg) (> arg 0)) 1 -1))) - (forward-sexp arg))) - -(defun godot-gdscript-nav--lisp-forward-sexp-safe (&optional arg) - "Safe version of standard `forward-sexp'. -When at end of sexp (i.e. looking at a opening/closing paren) -skips it instead of throwing an error. With positive ARG move -forward only one sexp, else move backwards." - (let* ((arg (if (or (not arg) (> arg 0)) 1 -1)) - (paren-regexp - (if (> arg 0) (godot-gdscript-rx close-paren) (godot-gdscript-rx open-paren))) - (search-fn - (if (> arg 0) #'re-search-forward #'re-search-backward))) - (condition-case nil - (godot-gdscript-nav--lisp-forward-sexp arg) - (error - (while (and (funcall search-fn paren-regexp nil t) - (godot-gdscript-syntax-context 'paren))))))) - -(defun godot-gdscript-nav--forward-sexp (&optional dir safe) - "Move to forward sexp. -With positive optional argument DIR direction move forward, else -backwards. When optional argument SAFE is non-nil do not throw -errors when at end of sexp, skip it instead." - (setq dir (or dir 1)) - (unless (= dir 0) - (let* ((forward-p (if (> dir 0) - (and (setq dir 1) t) - (and (setq dir -1) nil))) - (context-type (godot-gdscript-syntax-context-type))) - (cond - ((memq context-type '(string comment)) - ;; Inside of a string, get out of it. - (let ((forward-sexp-function)) - (forward-sexp dir))) - ((or (eq context-type 'paren) - (and forward-p (looking-at (godot-gdscript-rx open-paren))) - (and (not forward-p) - (eq (syntax-class (syntax-after (1- (point)))) - (car (string-to-syntax ")"))))) - ;; Inside a paren or looking at it, lisp knows what to do. - (if safe - (godot-gdscript-nav--lisp-forward-sexp-safe dir) - (godot-gdscript-nav--lisp-forward-sexp dir))) - (t - ;; This part handles the lispy feel of - ;; `godot-gdscript-nav-forward-sexp'. Knowing everything about the - ;; current context and the context of the next sexp tries to - ;; follow the lisp sexp motion commands in a symmetric manner. - (let* ((context - (cond - ((godot-gdscript-info-beginning-of-block-p) 'block-start) - ((godot-gdscript-info-end-of-block-p) 'block-end) - ((godot-gdscript-info-beginning-of-statement-p) 'statement-start) - ((godot-gdscript-info-end-of-statement-p) 'statement-end))) - (next-sexp-pos - (save-excursion - (if safe - (godot-gdscript-nav--lisp-forward-sexp-safe dir) - (godot-gdscript-nav--lisp-forward-sexp dir)) - (point))) - (next-sexp-context - (save-excursion - (goto-char next-sexp-pos) - (cond - ((godot-gdscript-info-beginning-of-block-p) 'block-start) - ((godot-gdscript-info-end-of-block-p) 'block-end) - ((godot-gdscript-info-beginning-of-statement-p) 'statement-start) - ((godot-gdscript-info-end-of-statement-p) 'statement-end) - ((godot-gdscript-info-statement-starts-block-p) 'starts-block) - ((godot-gdscript-info-statement-ends-block-p) 'ends-block))))) - (if forward-p - (cond ((and (not (eobp)) - (godot-gdscript-info-current-line-empty-p)) - (godot-gdscript-util-forward-comment dir) - (godot-gdscript-nav--forward-sexp dir)) - ((eq context 'block-start) - (godot-gdscript-nav-end-of-block)) - ((eq context 'statement-start) - (godot-gdscript-nav-end-of-statement)) - ((and (memq context '(statement-end block-end)) - (eq next-sexp-context 'ends-block)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-end-of-block)) - ((and (memq context '(statement-end block-end)) - (eq next-sexp-context 'starts-block)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-end-of-block)) - ((memq context '(statement-end block-end)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-end-of-statement)) - (t (goto-char next-sexp-pos))) - (cond ((and (not (bobp)) - (godot-gdscript-info-current-line-empty-p)) - (godot-gdscript-util-forward-comment dir) - (godot-gdscript-nav--forward-sexp dir)) - ((eq context 'block-end) - (godot-gdscript-nav-beginning-of-block)) - ((eq context 'statement-end) - (godot-gdscript-nav-beginning-of-statement)) - ((and (memq context '(statement-start block-start)) - (eq next-sexp-context 'starts-block)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-beginning-of-block)) - ((and (memq context '(statement-start block-start)) - (eq next-sexp-context 'ends-block)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-beginning-of-block)) - ((memq context '(statement-start block-start)) - (goto-char next-sexp-pos) - (godot-gdscript-nav-beginning-of-statement)) - (t (goto-char next-sexp-pos)))))))))) - -(defun godot-gdscript-nav-forward-sexp (&optional arg) - "Move forward across expressions. -With ARG, do it that many times. Negative arg -N means move -backward N times." - (interactive "^p") - (or arg (setq arg 1)) - (while (> arg 0) - (godot-gdscript-nav--forward-sexp 1) - (setq arg (1- arg))) - (while (< arg 0) - (godot-gdscript-nav--forward-sexp -1) - (setq arg (1+ arg)))) - -(defun godot-gdscript-nav-backward-sexp (&optional arg) - "Move backward across expressions. -With ARG, do it that many times. Negative arg -N means move -forward N times." - (interactive "^p") - (or arg (setq arg 1)) - (godot-gdscript-nav-forward-sexp (- arg))) - -(defun godot-gdscript-nav-forward-sexp-safe (&optional arg) - "Move forward safely across expressions. -With ARG, do it that many times. Negative arg -N means move -backward N times." - (interactive "^p") - (or arg (setq arg 1)) - (while (> arg 0) - (godot-gdscript-nav--forward-sexp 1 t) - (setq arg (1- arg))) - (while (< arg 0) - (godot-gdscript-nav--forward-sexp -1 t) - (setq arg (1+ arg)))) - -(defun godot-gdscript-nav-backward-sexp-safe (&optional arg) - "Move backward safely across expressions. -With ARG, do it that many times. Negative arg -N means move -forward N times." - (interactive "^p") - (or arg (setq arg 1)) - (godot-gdscript-nav-forward-sexp-safe (- arg))) - -(defun godot-gdscript-nav--up-list (&optional dir) - "Internal implementation of `godot-gdscript-nav-up-list'. -DIR is always 1 or -1 and comes sanitized from -`godot-gdscript-nav-up-list' calls." - (let ((context (godot-gdscript-syntax-context-type)) - (forward-p (> dir 0))) - (cond - ((memq context '(string comment))) - ((eq context 'paren) - (let ((forward-sexp-function)) - (up-list dir))) - ((and forward-p (godot-gdscript-info-end-of-block-p)) - (let ((parent-end-pos - (save-excursion - (let ((indentation (and - (godot-gdscript-nav-beginning-of-block) - (current-indentation)))) - (while (and indentation - (> indentation 0) - (>= (current-indentation) indentation) - (godot-gdscript-nav-backward-block))) - (godot-gdscript-nav-end-of-block))))) - (and (> (or parent-end-pos (point)) (point)) - (goto-char parent-end-pos)))) - (forward-p (godot-gdscript-nav-end-of-block)) - ((and (not forward-p) - (> (current-indentation) 0) - (godot-gdscript-info-beginning-of-block-p)) - (let ((prev-block-pos - (save-excursion - (let ((indentation (current-indentation))) - (while (and (godot-gdscript-nav-backward-block) - (>= (current-indentation) indentation)))) - (point)))) - (and (> (point) prev-block-pos) - (goto-char prev-block-pos)))) - ((not forward-p) (godot-gdscript-nav-beginning-of-block))))) - -(defun godot-gdscript-nav-up-list (&optional arg) - "Move forward out of one level of parentheses (or blocks). -With ARG, do this that many times. -A negative argument means move backward but still to a less deep spot. -This command assumes point is not in a string or comment." - (interactive "^p") - (or arg (setq arg 1)) - (while (> arg 0) - (godot-gdscript-nav--up-list 1) - (setq arg (1- arg))) - (while (< arg 0) - (godot-gdscript-nav--up-list -1) - (setq arg (1+ arg)))) - -(defun godot-gdscript-nav-backward-up-list (&optional arg) - "Move backward out of one level of parentheses (or blocks). -With ARG, do this that many times. -A negative argument means move forward but still to a less deep spot. -This command assumes point is not in a string or comment." - (interactive "^p") - (or arg (setq arg 1)) - (godot-gdscript-nav-up-list (- arg))) - -(defun godot-gdscript-nav-if-name-main () - "Move point at the beginning the __main__ block. -When \"if __name__ == '__main__':\" is found returns its -position, else returns nil." - (interactive) - (let ((point (point)) - (found (catch 'found - (goto-char (point-min)) - (while (re-search-forward - (godot-gdscript-rx line-start - "if" (+ space) - "__name__" (+ space) - "==" (+ space) - (group-n 1 (or ?\" ?\')) - "__main__" (backref 1) (* space) ":") - nil t) - (when (not (godot-gdscript-syntax-context-type)) - (beginning-of-line) - (throw 'found t)))))) - (if found - (point) - (ignore (goto-char point))))) - -;;; Shell integration - -(defcustom godot-gdscript-shell-buffer-name "Godot-GDScript" - "Default buffer name for Godot-GDScript interpreter." - :type 'string - :group 'godot-gdscript - :safe 'stringp) - -(defcustom godot-gdscript-shell-interpreter "godot-gdscript" - "Default Godot-GDScript interpreter for shell." - :type 'string - :group 'godot-gdscript) - -(defcustom godot-gdscript-shell-internal-buffer-name "godot-gdscript internal" - "Default buffer name for the Internal Godot-GDScript interpreter." - :type 'string - :group 'godot-gdscript - :safe 'stringp) - -(defcustom godot-gdscript-shell-interpreter-args "-i" - "Default arguments for the Godot-GDScript interpreter." - :type 'string - :group 'godot-gdscript) - -(defcustom godot-gdscript-shell-interpreter-interactive-arg "-i" - "Interpreter argument to force it to run interactively." - :type 'string - :version "24.4") - -(defcustom godot-gdscript-shell-prompt-detect-enabled t - "Non-nil enables autodetection of interpreter prompts." - :type 'boolean - :safe 'booleanp - :version "24.4") - -(defcustom godot-gdscript-shell-prompt-detect-failure-warning t - "Non-nil enables warnings when detection of prompts fail." - :type 'boolean - :safe 'booleanp - :version "24.4") - -(defcustom godot-gdscript-shell-prompt-input-regexps - '(">>> " "\\.\\.\\. " ; Godot-GDScript - "In \\[[0-9]+\\]: " ; IGodot-GDScript - ;; Using ipdb outside IGodot-GDScript may fail to cleanup and leave static - ;; IGodot-GDScript prompts activated, this adds some safeguard for that. - "In : " "\\.\\.\\.: ") - "List of regular expressions matching input prompts." - :type '(repeat string) - :version "24.4") - -(defcustom godot-gdscript-shell-prompt-output-regexps - '("" ; Godot-GDScript - "Out\\[[0-9]+\\]: " ; IGodot-GDScript - "Out :") ; ipdb safeguard - "List of regular expressions matching output prompts." - :type '(repeat string) - :version "24.4") - -(defcustom godot-gdscript-shell-prompt-regexp ">>> " - "Regular expression matching top level input prompt of Godot-GDScript shell. -It should not contain a caret (^) at the beginning." - :type 'string) - -(defcustom godot-gdscript-shell-prompt-block-regexp "\\.\\.\\. " - "Regular expression matching block input prompt of Godot-GDScript shell. -It should not contain a caret (^) at the beginning." - :type 'string) - -(defcustom godot-gdscript-shell-prompt-output-regexp "" - "Regular expression matching output prompt of Godot-GDScript shell. -It should not contain a caret (^) at the beginning." - :type 'string) - -(defcustom godot-gdscript-shell-prompt-pdb-regexp "[(<]*[Ii]?[Pp]db[>)]+ " - "Regular expression matching pdb input prompt of Godot-GDScript shell. -It should not contain a caret (^) at the beginning." - :type 'string) - -(defcustom godot-gdscript-shell-enable-font-lock t - "Should syntax highlighting be enabled in the Godot-GDScript shell buffer? -Restart the Godot-GDScript shell after changing this variable for -it to take effect." - :type 'boolean - :group 'godot-gdscript - :safe 'booleanp) - -(defcustom godot-gdscript-shell-unbuffered t - "Should shell output be unbuffered?. -When non-nil, this may prevent delayed and missing output in the -Godot-GDScript shell. See commentary for details." - :type 'boolean - :group 'godot-gdscript - :safe 'booleanp) - -(defcustom godot-gdscript-shell-process-environment nil - "List of environment variables for Godot-GDScript shell. -This variable follows the same rules as `process-environment' -since it merges with it before the process creation routines are -called. When this variable is nil, the Godot-GDScript shell is run with -the default `process-environment'." - :type '(repeat string) - :group 'godot-gdscript - :safe 'listp) - -(defcustom godot-gdscript-shell-extra-godot-gdscriptpaths nil - "List of extra Godot-GDScriptpaths for Godot-GDScript shell. -The values of this variable are added to the existing value of -GODOT-GDSCRIPTPATH in the `process-environment' variable." - :type '(repeat string) - :group 'godot-gdscript - :safe 'listp) - -(defcustom godot-gdscript-shell-exec-path nil - "List of path to search for binaries. -This variable follows the same rules as `exec-path' since it -merges with it before the process creation routines are called. -When this variable is nil, the Godot-GDScript shell is run with the -default `exec-path'." - :type '(repeat string) - :group 'godot-gdscript - :safe 'listp) - -(defcustom godot-gdscript-shell-virtualenv-path nil - "Path to virtualenv root. -This variable, when set to a string, makes the values stored in -`godot-gdscript-shell-process-environment' and -`godot-gdscript-shell-exec-path' to be modified properly so -shells are started with the specified virtualenv." - :type '(choice (const nil) string) - :group 'godot-gdscript - :safe 'stringp) - -(defcustom godot-gdscript-shell-setup-codes '(godot-gdscript-shell-completion-setup-code - godot-gdscript-ffap-setup-code) - "List of code run by `godot-gdscript-shell-send-setup-codes'." - :type '(repeat symbol) - :group 'godot-gdscript - :safe 'listp) - -(defcustom godot-gdscript-shell-compilation-regexp-alist - `((,(rx line-start (1+ (any " \t")) "File \"" - (group (1+ (not (any "\"<")))) ; avoid `' &c - "\", line " (group (1+ digit))) - 1 2) - (,(rx " in file " (group (1+ not-newline)) " on line " - (group (1+ digit))) - 1 2) - (,(rx line-start "> " (group (1+ (not (any "(\"<")))) - "(" (group (1+ digit)) ")" (1+ (not (any "("))) "()") - 1 2)) - "`compilation-error-regexp-alist' for inferior Godot-GDScript." - :type '(alist string) - :group 'godot-gdscript) - -(defvar godot-gdscript-shell--prompt-calculated-input-regexp nil - "Calculated input prompt regexp for inferior Godot-GDScript shell. -Do not set this variable directly, instead use -`godot-gdscript-shell-prompt-set-calculated-regexps'.") - -(defvar godot-gdscript-shell--prompt-calculated-output-regexp nil - "Calculated output prompt regexp for inferior Godot-GDScript shell. -Do not set this variable directly, instead use -`godot-gdscript-shell-set-prompt-regexp'.") - -(defun godot-gdscript-shell-prompt-detect () - "Detect prompts for the current `godot-gdscript-shell-interpreter'. -When prompts can be retrieved successfully from the -`godot-gdscript-shell-interpreter' run with -`godot-gdscript-shell-interpreter-interactive-arg', returns a list of -three elements, where the first two are input prompts and the -last one is an output prompt. When no prompts can be detected -and `godot-gdscript-shell-prompt-detect-failure-warning' is non-nil, -shows a warning with instructions to avoid hangs and returns nil. -When `godot-gdscript-shell-prompt-detect-enabled' is nil avoids any -detection and just returns nil." - (when godot-gdscript-shell-prompt-detect-enabled - ;; FIXME This check causes Godot to run again, opening a new window. - (let* ((process-environment (godot-gdscript-shell-calculate-process-environment)) - (exec-path (godot-gdscript-shell-calculate-exec-path)) - (code (concat - "import sys\n" - "ps = [getattr(sys, 'ps%s' % i, '') for i in range(1,4)]\n" - ;; JSON is built manually for compatibility - "ps_json = '\\n[\"%s\", \"%s\", \"%s\"]\\n' % tuple(ps)\n" - "print (ps_json)\n" - "sys.exit(0)\n")) - (output - (with-temp-buffer - ;; TODO: improve error handling by using - ;; `condition-case' and displaying the error message to - ;; the user in the no-prompts warning. - (ignore-errors - (let ((code-file (godot-gdscript-shell--save-temp-file code))) - ;; Use `process-file' as it is remote-host friendly. - (process-file - godot-gdscript-shell-interpreter - code-file - '(t nil) - nil - godot-gdscript-shell-interpreter-interactive-arg) - ;; Try to cleanup - (delete-file code-file))) - (buffer-string))) - (prompts - (catch 'prompts - (dolist (line (split-string output "\n" t)) - (let ((res - ;; Check if current line is a valid JSON array - (and (string= (substring line 0 2) "[\"") - (ignore-errors - ;; Return prompts as a list, not vector - (append (json-read-from-string line) nil))))) - ;; The list must contain 3 strings, where the first - ;; is the input prompt, the second is the block - ;; prompt and the last one is the output prompt. The - ;; input prompt is the only one that can't be empty. - (when (and (= (length res) 3) - (cl-every #'stringp res) - (not (string= (car res) ""))) - (throw 'prompts res)))) - nil))) - (when (and (not prompts) - godot-gdscript-shell-prompt-detect-failure-warning) - (warn - (concat - "Godot-GDScript shell prompts cannot be detected.\n" - "If your emacs session hangs when starting Godot-GDScript shells\n" - "recover with `keyboard-quit' and then try fixing the\n" - "interactive flag for your interpreter by adjusting the\n" - "`godot-gdscript-shell-interpreter-interactive-arg' or add regexps\n" - "matching shell prompts in the directory-local friendly vars:\n" - " + `godot-gdscript-shell-prompt-regexp'\n" - " + `godot-gdscript-shell-prompt-block-regexp'\n" - " + `godot-gdscript-shell-prompt-output-regexp'\n" - "Or alternatively in:\n" - " + `godot-gdscript-shell-prompt-input-regexps'\n" - " + `godot-gdscript-shell-prompt-output-regexps'"))) - prompts))) - -(defun godot-gdscript-shell-prompt-validate-regexps () - "Validate all user provided regexps for prompts. -Signals `user-error' if any of these vars contain invalid -regexps: `godot-gdscript-shell-prompt-regexp', -`godot-gdscript-shell-prompt-block-regexp', -`godot-gdscript-shell-prompt-pdb-regexp', -`godot-gdscript-shell-prompt-output-regexp', -`godot-gdscript-shell-prompt-input-regexps', -`godot-gdscript-shell-prompt-output-regexps'." - (dolist (symbol (list 'godot-gdscript-shell-prompt-input-regexps - 'godot-gdscript-shell-prompt-output-regexps - 'godot-gdscript-shell-prompt-regexp - 'godot-gdscript-shell-prompt-block-regexp - 'godot-gdscript-shell-prompt-pdb-regexp - 'godot-gdscript-shell-prompt-output-regexp)) - (dolist (regexp (let ((regexps (symbol-value symbol))) - (if (listp regexps) - regexps - (list regexps)))) - (when (not (godot-gdscript-util-valid-regexp-p regexp)) - (user-error "Invalid regexp %s in `%s'" - regexp symbol))))) - -(defun godot-gdscript-shell-prompt-set-calculated-regexps () - "Detect and set input and output prompt regexps. -Build and set the values for -`godot-gdscript-shell-input-prompt-regexp' and -`godot-gdscript-shell-output-prompt-regexp' using the values from -`godot-gdscript-shell-prompt-regexp', -`godot-gdscript-shell-prompt-block-regexp', -`godot-gdscript-shell-prompt-pdb-regexp', -`godot-gdscript-shell-prompt-output-regexp', -`godot-gdscript-shell-prompt-input-regexps', -`godot-gdscript-shell-prompt-output-regexps' and detected prompts -from `godot-gdscript-shell-prompt-detect'." - (when (not (and godot-gdscript-shell--prompt-calculated-input-regexp - godot-gdscript-shell--prompt-calculated-output-regexp)) - (let* ((detected-prompts (godot-gdscript-shell-prompt-detect)) - (input-prompts nil) - (output-prompts nil) - (build-regexp - (lambda (prompts) - (concat "^\\(" - (mapconcat #'identity - (sort prompts - (lambda (a b) - (let ((length-a (length a)) - (length-b (length b))) - (if (= length-a length-b) - (string< a b) - (> (length a) (length b)))))) - "\\|") - "\\)")))) - ;; Validate ALL regexps - (godot-gdscript-shell-prompt-validate-regexps) - ;; Collect all user defined input prompts - (dolist (prompt (append godot-gdscript-shell-prompt-input-regexps - (list godot-gdscript-shell-prompt-regexp - godot-gdscript-shell-prompt-block-regexp - godot-gdscript-shell-prompt-pdb-regexp))) - (cl-pushnew prompt input-prompts :test #'string=)) - ;; Collect all user defined output prompts - (dolist (prompt (cons godot-gdscript-shell-prompt-output-regexp - godot-gdscript-shell-prompt-output-regexps)) - (cl-pushnew prompt output-prompts :test #'string=)) - ;; Collect detected prompts if any - (when detected-prompts - (dolist (prompt (butlast detected-prompts)) - (setq prompt (regexp-quote prompt)) - (cl-pushnew prompt input-prompts :test #'string=)) - (cl-pushnew (regexp-quote - (car (last detected-prompts))) - output-prompts :test #'string=)) - ;; Set input and output prompt regexps from collected prompts - (setq godot-gdscript-shell--prompt-calculated-input-regexp - (funcall build-regexp input-prompts) - godot-gdscript-shell--prompt-calculated-output-regexp - (funcall build-regexp output-prompts))))) - -(defun godot-gdscript-shell-get-process-name (dedicated) - "Calculate the appropriate process name for inferior Godot-GDScript process. -If DEDICATED is t and the variable `buffer-file-name' is non-nil -returns a string with the form -`godot-gdscript-shell-buffer-name'[variable `buffer-file-name'] -else returns the value of `godot-gdscript-shell-buffer-name'." - (let ((process-name - (if (and dedicated - buffer-file-name) - (format "%s[%s]" godot-gdscript-shell-buffer-name buffer-file-name) - (format "%s" godot-gdscript-shell-buffer-name)))) - process-name)) - -(defun godot-gdscript-shell-internal-get-process-name () - "Calculate the appropriate process name for internal Godot-GDScript process. -The name is calculated from -`godot-gdscript-shell-global-buffer-name' and a hash of all -relevant global shell settings in order to ensure uniqueness for -different types of configurations." - (format "%s [%s]" - godot-gdscript-shell-internal-buffer-name - (md5 - (concat - godot-gdscript-shell-interpreter - godot-gdscript-shell-interpreter-args - godot-gdscript-shell--prompt-calculated-input-regexp - godot-gdscript-shell--prompt-calculated-output-regexp - (mapconcat #'symbol-value godot-gdscript-shell-setup-codes "") - (mapconcat #'identity godot-gdscript-shell-process-environment "") - (mapconcat #'identity godot-gdscript-shell-extra-godot-gdscriptpaths "") - (mapconcat #'identity godot-gdscript-shell-exec-path "") - (or godot-gdscript-shell-virtualenv-path "") - (mapconcat #'identity godot-gdscript-shell-exec-path ""))))) - -(defun godot-gdscript-shell-parse-command () ;FIXME: why name it "parse"? - "Calculate the string used to execute the inferior Godot-GDScript process." - ;; FIXME: process-environment doesn't seem to be used anywhere within - ;; this let. - (let ((process-environment (godot-gdscript-shell-calculate-process-environment)) - (exec-path (godot-gdscript-shell-calculate-exec-path))) - (format "%s %s" - ;; FIXME: Why executable-find? - (shell-quote-argument - (executable-find godot-gdscript-shell-interpreter)) - godot-gdscript-shell-interpreter-args))) - -(defun godot-gdscript-shell-calculate-process-environment () - "Calculate process environment given `godot-gdscript-shell-virtualenv-path'." - (let ((process-environment (append - godot-gdscript-shell-process-environment - process-environment nil)) - (virtualenv (if godot-gdscript-shell-virtualenv-path - (directory-file-name godot-gdscript-shell-virtualenv-path) - nil))) - (when godot-gdscript-shell-unbuffered - (setenv "GODOT-GDSCRIPTUNBUFFERED" "1")) - (when godot-gdscript-shell-extra-godot-gdscriptpaths - (setenv "GODOT-GDSCRIPTPATH" - (format "%s%s%s" - (mapconcat 'identity - godot-gdscript-shell-extra-godot-gdscriptpaths - path-separator) - path-separator - (or (getenv "GODOT-GDSCRIPTPATH") "")))) - (if (not virtualenv) - process-environment - (setenv "GODOT-GDSCRIPTHOME" nil) - (setenv "PATH" (format "%s/bin%s%s" - virtualenv path-separator - (or (getenv "PATH") ""))) - (setenv "VIRTUAL_ENV" virtualenv)) - process-environment)) - -(defun godot-gdscript-shell-calculate-exec-path () - "Calculate exec path given `godot-gdscript-shell-virtualenv-path'." - (let ((path (append godot-gdscript-shell-exec-path - exec-path nil))) ;FIXME: Why nil? - (if (not godot-gdscript-shell-virtualenv-path) - path - (cons (expand-file-name "bin" godot-gdscript-shell-virtualenv-path) - path)))) - -(defun godot-gdscript-comint-output-filter-function (output) - "Hook run after content is put into comint buffer. -OUTPUT is a string with the contents of the buffer." - (ansi-color-filter-apply output)) - -(defvar godot-gdscript-shell--parent-buffer nil) - -(defvar godot-gdscript-shell-output-syntax-table - (let ((table (make-syntax-table godot-gdscript-dotty-syntax-table))) - (modify-syntax-entry ?\' "." table) - (modify-syntax-entry ?\" "." table) - (modify-syntax-entry ?\( "." table) - (modify-syntax-entry ?\[ "." table) - (modify-syntax-entry ?\{ "." table) - (modify-syntax-entry ?\) "." table) - (modify-syntax-entry ?\] "." table) - (modify-syntax-entry ?\} "." table) - table) - "Syntax table for shell output. -It makes parens and quotes be treated as punctuation chars.") - -(define-derived-mode inferior-godot-gdscript-mode comint-mode "Inferior Godot-GDScript" - "Major mode for Godot-GDScript inferior process. -Runs a Godot-GDScript interpreter as a subprocess of Emacs, with -Godot-GDScript I/O through an Emacs buffer. Variables -`godot-gdscript-shell-interpreter' and -`godot-gdscript-shell-interpreter-args' control which -Godot-GDScript interpreter is run. Variables -`godot-gdscript-shell-prompt-regexp', -`godot-gdscript-shell-prompt-output-regexp', -`godot-gdscript-shell-prompt-block-regexp', -`godot-gdscript-shell-enable-font-lock', -`godot-gdscript-shell-completion-setup-code', -`godot-gdscript-shell-completion-string-code', -`godot-gdscript-ffap-setup-code' and -`godot-gdscript-ffap-string-code' can customize this mode for -different Godot-GDScript interpreters. - -You can also add additional setup code to be run at -initialization of the interpreter via `godot-gdscript-shell-setup-codes' -variable. - -\(Type \\[describe-mode] in the process buffer for a list of commands.)" - (let ((interpreter godot-gdscript-shell-interpreter) - (args godot-gdscript-shell-interpreter-args)) - (when godot-gdscript-shell--parent-buffer - (godot-gdscript-util-clone-local-variables godot-gdscript-shell--parent-buffer)) - ;; Users can override default values for these vars when calling - ;; `godot-gdscript-run-script'. This ensures new values let-bound in - ;; `godot-gdscript-shell-make-comint' are locally set. - (set (make-local-variable 'godot-gdscript-shell-interpreter) interpreter) - (set (make-local-variable 'godot-gdscript-shell-interpreter-args) args)) - (set (make-local-variable 'godot-gdscript-shell--prompt-calculated-input-regexp) nil) - (set (make-local-variable 'godot-gdscript-shell--prompt-calculated-output-regexp) nil) - ;; FIXME Causes window to be duplicated in `godot-gdscript-shell-prompt-detect`. - ;; (godot-gdscript-shell-prompt-set-calculated-regexps) - ;; As Godot's terminal does not accept input, we avoid font-locking for now. - ;; (setq comint-prompt-regexp godot-gdscript-shell--prompt-calculated-input-regexp) - ;; (setq mode-line-process '(":%s")) - ;; (make-local-variable 'comint-output-filter-functions) - ;; (add-hook 'comint-output-filter-functions - ;; 'godot-gdscript-comint-output-filter-function) - ;; (add-hook 'comint-output-filter-functions - ;; 'godot-gdscript-pdbtrack-comint-output-filter-function) - ;; (set (make-local-variable 'compilation-error-regexp-alist) - ;; godot-gdscript-shell-compilation-regexp-alist) - ;; (define-key inferior-godot-gdscript-mode-map [remap complete-symbol] - ;; 'completion-at-point) - ;; (add-hook 'completion-at-point-functions - ;; #'godot-gdscript-shell-completion-complete-at-point nil 'local) - ;; (add-hook 'comint-dynamic-complete-functions ;FIXME: really? - ;; #'godot-gdscript-shell-completion-complete-at-point nil 'local) - ;; (define-key inferior-godot-gdscript-mode-map "\t" - ;; 'godot-gdscript-shell-completion-complete-or-indent) - ;; (make-local-variable 'godot-gdscript-pdbtrack-buffers-to-kill) - ;; (make-local-variable 'godot-gdscript-pdbtrack-tracked-buffer) - ;; (make-local-variable 'godot-gdscript-shell-internal-last-output) - ;; (when godot-gdscript-shell-enable-font-lock - ;; (set-syntax-table godot-gdscript-mode-syntax-table) - ;; (set (make-local-variable 'font-lock-defaults) - ;; '(godot-gdscript-font-lock-keywords nil nil nil nil)) - ;; (set (make-local-variable 'syntax-propertize-function) - ;; (eval - ;; ;; XXX: Unfortunately eval is needed here to make use of the - ;; ;; dynamic value of `comint-prompt-regexp'. - ;; `(syntax-propertize-rules - ;; (,comint-prompt-regexp - ;; (0 (ignore - ;; (put-text-property - ;; comint-last-input-start end 'syntax-table - ;; godot-gdscript-shell-output-syntax-table) - ;; ;; XXX: This might look weird, but it is the easiest - ;; ;; way to ensure font lock gets cleaned up before the - ;; ;; current prompt, which is needed for unclosed - ;; ;; strings to not mess up with current input. - ;; (font-lock-unfontify-region comint-last-input-start end)))) - ;; (,(godot-gdscript-rx string-delimiter) - ;; (0 (ignore - ;; (and (not (eq (get-text-property start 'field) 'output)) - ;; (godot-gdscript-syntax-stringify))))))))) - ;; (compilation-shell-minor-mode 1) -) - -(defun godot-gdscript-shell-make-comint (cmd proc-name &optional pop internal) - "Create a Godot-GDScript shell comint buffer. -CMD is the Godot-GDScript command to be executed and PROC-NAME is the -process name the comint buffer will get. After the comint buffer -is created the `inferior-godot-gdscript-mode' is activated. When -optional argument POP is non-nil the buffer is shown. When -optional argument INTERNAL is non-nil this process is run on a -buffer with a name that starts with a space, following the Emacs -convention for temporary/internal buffers, and also makes sure -the user is not queried for confirmation when the process is -killed." - (save-excursion - (let* ((proc-buffer-name - (format (if (not internal) "*%s*" " *%s*") proc-name)) - (process-environment (godot-gdscript-shell-calculate-process-environment)) - (exec-path (godot-gdscript-shell-calculate-exec-path))) - (when (not (comint-check-proc proc-buffer-name)) - (let* ((cmdlist (split-string-and-unquote cmd)) - (interpreter (car cmdlist)) - (args (cdr cmdlist)) - (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name - interpreter nil args)) - (godot-gdscript-shell--parent-buffer (current-buffer)) - (process (get-buffer-process buffer)) - ;; As the user may have overridden default values for - ;; these vars on `godot-gdscript-run-script', let-binding them allows - ;; to have the new right values in all setup code - ;; that's is done in `inferior-godot-gdscript-mode', which is - ;; important, especially for prompt detection. - (godot-gdscript-shell-interpreter interpreter) - (godot-gdscript-shell-interpreter-args - (mapconcat #'identity args " "))) - (with-current-buffer buffer - (inferior-godot-gdscript-mode)) - (accept-process-output process) - (and pop (pop-to-buffer buffer t)) - (and internal (set-process-query-on-exit-flag process nil)))) - proc-buffer-name))) - -;;;###autoload -(defun godot-gdscript-run-script (cmd &optional dedicated show) - "Run an inferior Godot-GDScript process. -Input and output via buffer named after -`godot-gdscript-shell-buffer-name'. If there is a process already -running in that buffer, just switch to it. - -With argument, allows you to define CMD so you can edit the -command used to call the interpreter and define DEDICATED, so a -dedicated process for the current buffer is open. When numeric -prefix arg is other than 0 or 4 do not SHOW. - -Runs the hook `inferior-godot-gdscript-mode-hook' after -`comint-mode-hook' is run. (Type \\[describe-mode] in the -process buffer for a list of commands.)" - (interactive - (if current-prefix-arg - (list - (read-string "Run Godot-GDScript: " (godot-gdscript-shell-parse-command)) - (y-or-n-p "Make dedicated process? ") - (= (prefix-numeric-value current-prefix-arg) 4)) - (list (godot-gdscript-shell-parse-command) nil t))) - (godot-gdscript-shell-make-comint - cmd (godot-gdscript-shell-get-process-name dedicated) show) - dedicated) - -(defun godot-gdscript-run-script-internal () - "Run an inferior Internal Godot-GDScript process. -Input and output via buffer named after -`godot-gdscript-shell-internal-buffer-name' and what -`godot-gdscript-shell-internal-get-process-name' returns. - -This new kind of shell is intended to be used for generic -communication related to defined configurations; the main -difference with global or dedicated shells is that these ones are -attached to a configuration, not a buffer. This means that can be -used for example to retrieve the sys.path and other stuff, -without messing with user shells. Note that -`godot-gdscript-shell-enable-font-lock' and -`inferior-godot-gdscript-mode-hook' are set to nil for these -shells, so setup codes are not sent at startup." - (let ((godot-gdscript-shell-enable-font-lock nil) - (inferior-godot-gdscript-mode-hook nil)) - (get-buffer-process - (godot-gdscript-shell-make-comint - (godot-gdscript-shell-parse-command) - (godot-gdscript-shell-internal-get-process-name) nil t)))) - -(defun godot-gdscript-shell-get-buffer () - "Return inferior Godot-GDScript buffer for current buffer." - (let* ((dedicated-proc-name (godot-gdscript-shell-get-process-name t)) - (dedicated-proc-buffer-name (format "*%s*" dedicated-proc-name)) - (global-proc-name (godot-gdscript-shell-get-process-name nil)) - (global-proc-buffer-name (format "*%s*" global-proc-name)) - (dedicated-running (comint-check-proc dedicated-proc-buffer-name)) - (global-running (comint-check-proc global-proc-buffer-name))) - ;; Always prefer dedicated - (or (and dedicated-running dedicated-proc-buffer-name) - (and global-running global-proc-buffer-name)))) - -(defun godot-gdscript-shell-get-process () - "Return inferior godot-gdscript process for current buffer." - (get-buffer-process (godot-gdscript-shell-get-buffer))) - -(defun godot-gdscript-shell-get-or-create-process (&optional cmd dedicated show) - "Get or create an inferior GDScript process for current buffer and return it. -Arguments CMD, DEDICATED and SHOW are those of -`godot-gdscript-run-script' and are used to start the shell. If those -arguments are not provided, `godot-gdscript-run-script' is called -interactively and the user will be asked for their values." - (let* ((dedicated-proc-name (godot-gdscript-shell-get-process-name t)) - (dedicated-proc-buffer-name (format "*%s*" dedicated-proc-name)) - (global-proc-name (godot-gdscript-shell-get-process-name nil)) - (global-proc-buffer-name (format "*%s*" global-proc-name)) - (dedicated-running (comint-check-proc dedicated-proc-buffer-name)) - (global-running (comint-check-proc global-proc-buffer-name)) - (current-prefix-arg 16)) - (when (and (not dedicated-running) (not global-running)) - (if (if (not cmd) - ;; XXX: Refactor code such that calling `godot-gdscript-run-script' - ;; interactively is not needed anymore. - (call-interactively 'godot-gdscript-run-script) - (godot-gdscript-run-script cmd dedicated show)) - (setq dedicated-running t) - (setq global-running t))) - ;; Always prefer dedicated - (get-buffer-process (if dedicated-running - dedicated-proc-buffer-name - global-proc-buffer-name)))) - -(defvar godot-gdscript-shell-internal-buffer nil - "Current internal shell buffer for the current buffer. -This is really not necessary at all for the code to work but it's -there for compatibility with CEDET.") - -(defvar godot-gdscript-shell-internal-last-output nil - "Last output captured by the internal shell. -This is really not necessary at all for the code to work but it's -there for compatibility with CEDET.") - -(defun godot-gdscript-shell-internal-get-or-create-process () - "Get or create an inferior internal Godot-GDScript process." - (let* ((proc-name (godot-gdscript-shell-internal-get-process-name)) - (proc-buffer-name (format " *%s*" proc-name))) - (when (not (process-live-p proc-name)) - (godot-gdscript-run-script-internal) - (setq godot-gdscript-shell-internal-buffer proc-buffer-name) - ;; XXX: Why is this `sit-for' needed? - ;; `godot-gdscript-shell-make-comint' calls `accept-process-output' - ;; already but it is not helping to get proper output on - ;; 'gnu/linux when the internal shell process is not running and - ;; a call to `godot-gdscript-shell-internal-send-string' is issued. - (sit-for 0.1 t)) - (get-buffer-process proc-buffer-name))) - -(define-obsolete-function-alias - 'godot-gdscript-proc 'godot-gdscript-shell-internal-get-or-create-process "24.3") - -(define-obsolete-variable-alias - 'godot-gdscript-buffer 'godot-gdscript-shell-internal-buffer "24.3") - -(define-obsolete-variable-alias - 'godot-gdscript-preoutput-result 'godot-gdscript-shell-internal-last-output "24.3") - -(defun godot-gdscript-shell--save-temp-file (string) - "Save a temporary file with contents defined in STRING." - (let* ((temporary-file-directory - (if (file-remote-p default-directory) - (concat (file-remote-p default-directory) "/tmp") - temporary-file-directory)) - (temp-file-name (make-temp-file "py")) - (coding-system-for-write (godot-gdscript-info-encoding))) - (with-temp-file temp-file-name - (insert string) - (delete-trailing-whitespace)) - temp-file-name)) - -(defun godot-gdscript-shell-send-string (string &optional process) - "Send STRING to inferior Godot-GDScript PROCESS." - (interactive "sGodot-GDScript command: ") - (let ((process (or process (godot-gdscript-shell-get-or-create-process)))) - (if (string-match ".\n+." string) ;Multiline. - (let* ((temp-file-name (godot-gdscript-shell--save-temp-file string)) - (file-name (or (buffer-file-name) temp-file-name))) - (godot-gdscript-shell-send-file file-name process temp-file-name t)) - (comint-send-string process string) - (when (or (not (string-match "\n\\'" string)) - (string-match "\n[ \t].*\n?\\'" string)) - (comint-send-string process "\n"))))) - -(defvar godot-gdscript-shell-output-filter-in-progress nil) -(defvar godot-gdscript-shell-output-filter-buffer nil) - -(defun godot-gdscript-shell-output-filter (string) - "Filter used in `godot-gdscript-shell-send-string-no-output' to grab output. -STRING is the output received to this point from the process. -This filter saves received output from the process in -`godot-gdscript-shell-output-filter-buffer' and stops receiving -it after detecting a prompt at the end of the buffer." - (setq - string (ansi-color-filter-apply string) - godot-gdscript-shell-output-filter-buffer - (concat godot-gdscript-shell-output-filter-buffer string)) - (when (string-match - ;; XXX: It seems on OSX an extra carriage return is attached - ;; at the end of output, this handles that too. - (concat - "\r?\n" - ;; Remove initial caret from calculated regexp - (replace-regexp-in-string - (rx string-start ?^) "" - godot-gdscript-shell--prompt-calculated-input-regexp) - "$") - godot-gdscript-shell-output-filter-buffer) - ;; Output ends when `godot-gdscript-shell-output-filter-buffer' contains - ;; the prompt attached at the end of it. - (setq godot-gdscript-shell-output-filter-in-progress nil - godot-gdscript-shell-output-filter-buffer - (substring godot-gdscript-shell-output-filter-buffer - 0 (match-beginning 0))) - (when (string-match - godot-gdscript-shell--prompt-calculated-output-regexp - godot-gdscript-shell-output-filter-buffer) - ;; Some shells, like IGodot-GDScript might append a prompt before the - ;; output, clean that. - (setq godot-gdscript-shell-output-filter-buffer - (substring godot-gdscript-shell-output-filter-buffer (match-end 0))))) - "") - -(defun godot-gdscript-shell-send-string-no-output (string &optional process) - "Send STRING to PROCESS and inhibit output. -Return the output." - (let ((process (or process (godot-gdscript-shell-get-or-create-process))) - (comint-preoutput-filter-functions - '(godot-gdscript-shell-output-filter)) - (godot-gdscript-shell-output-filter-in-progress t) - (inhibit-quit t)) - (or - (with-local-quit - (godot-gdscript-shell-send-string string process) - (while godot-gdscript-shell-output-filter-in-progress - ;; `godot-gdscript-shell-output-filter' takes care of setting - ;; `godot-gdscript-shell-output-filter-in-progress' to NIL after it - ;; detects end of output. - (accept-process-output process)) - (prog1 - godot-gdscript-shell-output-filter-buffer - (setq godot-gdscript-shell-output-filter-buffer nil))) - (with-current-buffer (process-buffer process) - (comint-interrupt-subjob))))) - -(defun godot-gdscript-shell-internal-send-string (string) - "Send STRING to the Internal Godot-GDScript interpreter. -Returns the output. See `godot-gdscript-shell-send-string-no-output'." - ;; XXX Remove `godot-gdscript-shell-internal-last-output' once CEDET is - ;; updated to support this new mode. - (setq godot-gdscript-shell-internal-last-output - (godot-gdscript-shell-send-string-no-output - ;; Makes this function compatible with the old - ;; godot-gdscript-send-receive. (At least for CEDET). - (replace-regexp-in-string "_emacs_out +" "" string) - (godot-gdscript-shell-internal-get-or-create-process)))) - -(define-obsolete-function-alias - 'godot-gdscript-send-receive 'godot-gdscript-shell-internal-send-string "24.3") - -(define-obsolete-function-alias - 'godot-gdscript-send-string 'godot-gdscript-shell-internal-send-string "24.3") - -(defun godot-gdscript-shell-buffer-substring (start end &optional nomain) - "Send buffer substring from START to END formatted for shell. -This is a wrapper over `buffer-substring' that takes care of -different transformations for the code sent to be evaluated in -the godot-gdscript shell: - 1. When optional argument NOMAIN is non-nil everything under an - \"if __name__ == '__main__'\" block will be removed. - 2. When a subregion of the buffer is sent, it takes care of - appending extra empty lines so tracebacks are correct. - 3. When the region sent is a substring of the current buffer, a - coding cookie is added. - 4. Wraps indented regions under an \"if true:\" block so the - interpreter evaluates them correctly." - (let* ((substring (buffer-substring-no-properties start end)) - (starts-at-point-min-p (save-restriction - (widen) - (= (point-min) start))) - (encoding (godot-gdscript-info-encoding)) - (fillstr (when (not starts-at-point-min-p) - (concat - (format "# -*- coding: %s -*-\n" encoding) - (make-string - ;; Subtract 2 because of the coding cookie. - (- (line-number-at-pos start) 2) ?\n)))) - (toplevel-block-p (save-excursion - (goto-char start) - (or (zerop (line-number-at-pos start)) - (progn - (godot-gdscript-util-forward-comment 1) - (zerop (current-indentation))))))) - (with-temp-buffer - (godot-gdscript-mode) - (if fillstr (insert fillstr)) - (insert substring) - (goto-char (point-min)) - (when (not toplevel-block-p) - (insert "if true:") - (delete-region (point) (line-end-position))) - (when nomain - (let* ((if-name-main-start-end - (and nomain - (save-excursion - (when (godot-gdscript-nav-if-name-main) - (cons (point) - (progn (godot-gdscript-nav-forward-sexp-safe) - ;; Include ending newline - (forward-line 1) - (point))))))) - ;; Oh destructuring bind, how I miss you. - (if-name-main-start (car if-name-main-start-end)) - (if-name-main-end (cdr if-name-main-start-end)) - (fillstr (make-string - (- (line-number-at-pos if-name-main-end) - (line-number-at-pos if-name-main-start)) ?\n))) - (when if-name-main-start-end - (goto-char if-name-main-start) - (delete-region if-name-main-start if-name-main-end) - (insert fillstr)))) - ;; Ensure there's only one coding cookie in the generated string. - (goto-char (point-min)) - (when (looking-at-p (godot-gdscript-rx coding-cookie)) - (forward-line 1) - (when (looking-at-p (godot-gdscript-rx coding-cookie)) - (delete-region - (line-beginning-position) (line-end-position)))) - (buffer-substring-no-properties (point-min) (point-max))))) - -(defun godot-gdscript-shell-send-region (start end &optional send-main) - "Send the region delimited by START and END to inferior GDScript process. -When optional argument SEND-MAIN is non-nil, allow execution of -code inside blocks delimited by \"if __name__== '__main__':\". -When called interactively SEND-MAIN defaults to nil, unless it's -called with prefix argument." - (interactive "r\nP") - (let* ((string (godot-gdscript-shell-buffer-substring start end (not send-main))) - (process (godot-gdscript-shell-get-or-create-process)) - (original-string (buffer-substring-no-properties start end)) - (_ (string-match "\\`\n*\\(.*\\)" original-string))) - (message "Sent: %s..." (match-string 1 original-string)) - (godot-gdscript-shell-send-string string process))) - -(defun godot-gdscript-shell-send-buffer (&optional send-main) - "Send the entire buffer to inferior Godot-GDScript process. -When optional argument SEND-MAIN is non-nil, allow execution of -code inside blocks delimited by \"if __name__== '__main__':\". -When called interactively SEND-MAIN defaults to nil, unless it's -called with prefix argument." - (interactive "P") - (save-restriction - (widen) - (godot-gdscript-shell-send-region (point-min) (point-max) send-main))) - -(defun godot-gdscript-shell-send-defun (arg) - "Send the current defun to inferior Godot-GDScript process. -When argument ARG is non-nil do not include decorators." - (interactive "P") - (save-excursion - (godot-gdscript-shell-send-region - (progn - (end-of-line 1) - (while (and (or (godot-gdscript-nav-beginning-of-defun) - (beginning-of-line 1)) - (> (current-indentation) 0))) - (when (not arg) - (while (and (forward-line -1) - (looking-at (godot-gdscript-rx decorator)))) - (forward-line 1)) - (point-marker)) - (progn - (or (godot-gdscript-nav-end-of-defun) - (end-of-line 1)) - (point-marker))))) - -(defun godot-gdscript-shell-send-file (file-name &optional process temp-file-name - delete) - "Send FILE-NAME to inferior Godot-GDScript PROCESS. -If TEMP-FILE-NAME is passed then that file is used for processing -instead, while internally the shell will continue to use -FILE-NAME. If TEMP-FILE-NAME and DELETE are non-nil, then -TEMP-FILE-NAME is deleted after evaluation is performed." - (interactive "fFile to send: ") - (let* ((process (or process (godot-gdscript-shell-get-or-create-process))) - (encoding (with-temp-buffer - (insert-file-contents - (or temp-file-name file-name)) - (godot-gdscript-info-encoding))) - (file-name (expand-file-name - (or (file-remote-p file-name 'localname) - file-name))) - (temp-file-name (when temp-file-name - (expand-file-name - (or (file-remote-p temp-file-name 'localname) - temp-file-name))))) - (godot-gdscript-shell-send-string - (format - (concat - "import codecs, os;" - "__pyfile = codecs.open('''%s''', encoding='''%s''');" - "__code = __pyfile.read().encode('''%s''');" - "__pyfile.close();" - (when (and delete temp-file-name) - (format "os.remove('''%s''');" temp-file-name)) - "exec(compile(__code, '''%s''', 'exec'));") - (or temp-file-name file-name) encoding encoding file-name) - process))) - -(defun godot-gdscript-shell-switch-to-shell () - "Switch to inferior Godot-GDScript process buffer." - (interactive) - (pop-to-buffer (process-buffer (godot-gdscript-shell-get-or-create-process)) t)) - -(defun godot-gdscript-shell-send-setup-code () - "Send all setup code for shell. -This function takes the list of setup code to send from the -`godot-gdscript-shell-setup-codes' list." - (let ((process (get-buffer-process (current-buffer)))) - (dolist (code godot-gdscript-shell-setup-codes) - (when code - (message "Sent %s" code) - (godot-gdscript-shell-send-string - (symbol-value code) process))))) - -(add-hook 'inferior-godot-gdscript-mode-hook - #'godot-gdscript-shell-send-setup-code) - -;;; Shell completion - -(defcustom godot-gdscript-shell-completion-setup-code - "try: - import __builtin__ -except ImportError: - # Godot-Gdscript 3 - import builtins as __builtin__ -try: - import readline, rlcompleter -except: - def __GODOT-GDSCRIPT_EL_get_completions(text): - return [] -else: - def __GODOT-GDSCRIPT_EL_get_completions(text): - builtins = dir(__builtin__) - completions = [] - try: - splits = text.split() - is_module = splits and splits[0] in ('from', 'import') - is_igodot-gdscript = ('__IGODOT-GDSCRIPT__' in builtins or - '__IGODOT-GDSCRIPT__active' in builtins) - if is_module: - from IGodot-Gdscript.core.completerlib import module_completion - completions = module_completion(text.strip()) - elif is_igodot-gdscript and '__IP' in builtins: - completions = __IP.complete(text) - elif is_igodot-gdscript and 'get_igodot-gdscript' in builtins: - completions = get_igodot-gdscript().Completer.all_completions(text) - else: - i = 0 - while true: - res = readline.get_completer()(text, i) - if not res: - break - i += 1 - completions.append(res) - except: - pass - return completions" - "Code used to setup completion in inferior Godot-Gdscript processes." - :type 'string - :group 'godot-gdscript) - -(defcustom godot-gdscript-shell-completion-string-code - "';'.join(__GODOT-GDSCRIPT_EL_get_completions('''%s'''))\n" - "Gdscript code used to get a string of completions separated by semicolons. -The string passed to the function is the current Godot-GDScript name or -the full statement in the case of imports." - :type 'string - :group 'godot-gdscript) - -(define-obsolete-variable-alias - 'godot-gdscript-shell-completion-module-string-code - 'godot-gdscript-shell-completion-string-code - "24.4" - "Completion string code must also autocomplete modules.") - -(defcustom godot-gdscript-shell-completion-pdb-string-code - "';'.join(globals().keys() + locals().keys())" - "Gdscript code used to get completions separated by semicolons for [i]pdb." - :type 'string - :group 'godot-gdscript) - -(defun godot-gdscript-shell-completion-get-completions (process line input) - "Do completion at point for PROCESS. -LINE is used to detect the context on how to complete given INPUT." - (with-current-buffer (process-buffer process) - (let* ((prompt - ;; Get last prompt of the inferior process buffer (this - ;; intentionally avoids using `comint-last-prompt' because - ;; of incompatibilities with Emacs 24.x). - (save-excursion - (buffer-substring-no-properties - (line-beginning-position) ;End of prompt. - (re-search-backward "^")))) - (completion-code - ;; Check whether a prompt matches a pdb string, an import - ;; statement or just the standard prompt and use the - ;; correct godot-gdscript-shell-completion-*-code string - (cond ((and (> (length godot-gdscript-shell-completion-pdb-string-code) 0) - (string-match - (concat "^" godot-gdscript-shell-prompt-pdb-regexp) prompt)) - godot-gdscript-shell-completion-pdb-string-code) - ((string-match - godot-gdscript-shell--prompt-calculated-input-regexp prompt) - godot-gdscript-shell-completion-string-code) - (t nil))) - (input - (if (string-match - (godot-gdscript-rx line-start (* space) (or "from" "import") space) - line) - line - input))) - (and completion-code - (> (length input) 0) - (let ((completions - (godot-gdscript-util-strip-string - (godot-gdscript-shell-send-string-no-output - (format completion-code input) process)))) - (and (> (length completions) 2) - (split-string completions - "^'\\|^\"\\|;\\|'$\\|\"$" t))))))) - -(defun godot-gdscript-shell-completion-complete-at-point (&optional process) - "Perform completion at point in inferior Godot-Gdscript. -Optional argument PROCESS forces completions to be retrieved -using that one instead of current buffer's process." - (setq process (or process (get-buffer-process (current-buffer)))) - (let* ((start - (save-excursion - (with-syntax-table godot-gdscript-dotty-syntax-table - (let* ((paren-depth (car (syntax-ppss))) - (syntax-string "w_") - (syntax-list (string-to-syntax syntax-string))) - ;; Stop scanning for the beginning of the completion - ;; subject after the char before point matches a - ;; delimiter - (while (member - (car (syntax-after (1- (point)))) syntax-list) - (skip-syntax-backward syntax-string) - (when (or (equal (char-before) ?\)) - (equal (char-before) ?\")) - (forward-char -1)) - (while (or - ;; honor initial paren depth - (> (car (syntax-ppss)) paren-depth) - (godot-gdscript-syntax-context 'string)) - (forward-char -1))) - (point))))) - (end (point))) - (list start end - (completion-table-dynamic - (apply-partially - #'godot-gdscript-shell-completion-get-completions - process (buffer-substring-no-properties - (line-beginning-position) end)))))) - -(defun godot-gdscript-shell-completion-complete-or-indent () - "Complete or indent depending on the context. -If content before pointer is all whitespace, indent. -If not try to complete." - (interactive) - (if (string-match "^[[:space:]]*$" - (buffer-substring (comint-line-beginning-position) - (point-marker))) - (indent-for-tab-command) - (completion-at-point))) - -;;; PDB Track integration - -(defcustom godot-gdscript-pdbtrack-activate t - "Non-nil makes Godot-Gdscript shell enable pdbtracking." - :type 'boolean - :group 'godot-gdscript - :safe 'booleanp) - -(defcustom godot-gdscript-pdbtrack-stacktrace-info-regexp - "> \\([^\"(<]+\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()" - "Regular expression matching stacktrace information. -Used to extract the current line and module being inspected." - :type 'string - :group 'godot-gdscript - :safe 'stringp) - -(defvar godot-gdscript-pdbtrack-tracked-buffer nil - "Variable containing the value of the current tracked buffer. -Never set this variable directly, use -`godot-gdscript-pdbtrack-set-tracked-buffer' instead.") - -(defvar godot-gdscript-pdbtrack-buffers-to-kill nil - "List of buffers to be deleted after tracking finishes.") - -(defun godot-gdscript-pdbtrack-set-tracked-buffer (file-name) - "Set the buffer for FILE-NAME as the tracked buffer. -Internally it uses the `godot-gdscript-pdbtrack-tracked-buffer' variable. -Returns the tracked buffer." - (let ((file-buffer (get-file-buffer - (concat (file-remote-p default-directory) - file-name)))) - (if file-buffer - (setq godot-gdscript-pdbtrack-tracked-buffer file-buffer) - (setq file-buffer (find-file-noselect file-name)) - (when (not (member file-buffer godot-gdscript-pdbtrack-buffers-to-kill)) - (add-to-list 'godot-gdscript-pdbtrack-buffers-to-kill file-buffer))) - file-buffer)) - -(defun godot-gdscript-pdbtrack-comint-output-filter-function (output) - "Move overlay arrow to current pdb line in tracked buffer. -Argument OUTPUT is a string with the output from the comint process." - (when (and godot-gdscript-pdbtrack-activate (not (string= output ""))) - (let* ((full-output (ansi-color-filter-apply - (buffer-substring comint-last-input-end (point-max)))) - (line-number) - (file-name - (with-temp-buffer - (insert full-output) - ;; When the debugger encounters a pdb.set_trace() - ;; command, it prints a single stack frame. Sometimes - ;; it prints a bit of extra information about the - ;; arguments of the present function. When ipdb - ;; encounters an exception, it prints the _entire_ stack - ;; trace. To handle all of these cases, we want to find - ;; the _last_ stack frame printed in the most recent - ;; batch of output, then jump to the corresponding - ;; file/line number. - (goto-char (point-max)) - (when (re-search-backward godot-gdscript-pdbtrack-stacktrace-info-regexp nil t) - (setq line-number (string-to-number - (match-string-no-properties 2))) - (match-string-no-properties 1))))) - (if (and file-name line-number) - (let* ((tracked-buffer - (godot-gdscript-pdbtrack-set-tracked-buffer file-name)) - (shell-buffer (current-buffer)) - (tracked-buffer-window (get-buffer-window tracked-buffer)) - (tracked-buffer-line-pos)) - (with-current-buffer tracked-buffer - (set (make-local-variable 'overlay-arrow-string) "=>") - (set (make-local-variable 'overlay-arrow-position) (make-marker)) - (setq tracked-buffer-line-pos (progn - (goto-char (point-min)) - (forward-line (1- line-number)) - (point-marker))) - (when tracked-buffer-window - (set-window-point - tracked-buffer-window tracked-buffer-line-pos)) - (set-marker overlay-arrow-position tracked-buffer-line-pos)) - (pop-to-buffer tracked-buffer) - (switch-to-buffer-other-window shell-buffer)) - (when godot-gdscript-pdbtrack-tracked-buffer - (with-current-buffer godot-gdscript-pdbtrack-tracked-buffer - (set-marker overlay-arrow-position nil)) - (mapc #'(lambda (buffer) - (ignore-errors (kill-buffer buffer))) - godot-gdscript-pdbtrack-buffers-to-kill) - (setq godot-gdscript-pdbtrack-tracked-buffer nil - godot-gdscript-pdbtrack-buffers-to-kill nil))))) - output) - -;;; Symbol completion - -(defun godot-gdscript-completion-complete-at-point () - "Complete current symbol at point. -For this to work as best as possible you should call -`godot-gdscript-shell-send-buffer' from time to time so context in -inferior Godot-Gdscript process is updated properly." - (when (require 'company nil 'noerror) - (company-complete))) - -;;; Fill paragraph - -(defcustom godot-gdscript-fill-comment-function 'godot-gdscript-fill-comment - "Function to fill comments. -This is the function used by `godot-gdscript-fill-paragraph' to -fill comments." - :type 'symbol - :group 'godot-gdscript) - -(defcustom godot-gdscript-fill-string-function 'godot-gdscript-fill-string - "Function to fill strings. -This is the function used by `godot-gdscript-fill-paragraph' to -fill strings." - :type 'symbol - :group 'godot-gdscript) - -(defcustom godot-gdscript-fill-decorator-function 'godot-gdscript-fill-decorator - "Function to fill decorators. -This is the function used by `godot-gdscript-fill-paragraph' to -fill decorators." - :type 'symbol - :group 'godot-gdscript) - -(defcustom godot-gdscript-fill-paren-function 'godot-gdscript-fill-paren - "Function to fill parens. -This is the function used by `godot-gdscript-fill-paragraph' to -fill parens." - :type 'symbol - :group 'godot-gdscript) - -(defcustom godot-gdscript-fill-docstring-style 'pep-257 - "Style used to fill docstrings. -This affects `godot-gdscript-fill-string' behavior with regards to -triple quotes positioning. - -Possible values are `django', `onetwo', `pep-257', `pep-257-nn', -`symmetric', and nil. A value of nil won't care about quotes -position and will treat docstrings a normal string, any other -value may result in one of the following docstring styles: - -`django': - - \"\"\" - Process foo, return bar. - \"\"\" - - \"\"\" - Process foo, return bar. - - If processing fails throw ProcessingError. - \"\"\" - -`onetwo': - - \"\"\"Process foo, return bar.\"\"\" - - \"\"\" - Process foo, return bar. - - If processing fails throw ProcessingError. - - \"\"\" - -`pep-257': - - \"\"\"Process foo, return bar.\"\"\" - - \"\"\"Process foo, return bar. - - If processing fails throw ProcessingError. - - \"\"\" - -`pep-257-nn': - - \"\"\"Process foo, return bar.\"\"\" - - \"\"\"Process foo, return bar. - - If processing fails throw ProcessingError. - \"\"\" - -`symmetric': - - \"\"\"Process foo, return bar.\"\"\" - - \"\"\" - Process foo, return bar. - - If processing fails throw ProcessingError. - \"\"\"" - :type '(choice - (const :tag "Don't format docstrings" nil) - (const :tag "Django's coding standards style." django) - (const :tag "One newline and start and Two at end style." onetwo) - (const :tag "PEP-257 with 2 newlines at end of string." pep-257) - (const :tag "PEP-257 with 1 newline at end of string." pep-257-nn) - (const :tag "Symmetric style." symmetric)) - :group 'godot-gdscript - :safe (lambda (val) - (memq val '(django onetwo pep-257 pep-257-nn symmetric nil)))) - -(defun godot-gdscript-fill-paragraph (&optional justify) - "`fill-paragraph-function' handling multi-line strings and possibly comments. -If any of the current line is in or at the end of a multi-line string, -fill the string or the paragraph of it that point is in, preserving -the string's indentation. -Optional argument JUSTIFY defines if the paragraph should be justified." - (interactive "P") - (save-excursion - (cond - ;; Comments - ((godot-gdscript-syntax-context 'comment) - (funcall godot-gdscript-fill-comment-function justify)) - ;; Strings/Docstrings - ((save-excursion (or (godot-gdscript-syntax-context 'string) - (equal (string-to-syntax "|") - (syntax-after (point))))) - (funcall godot-gdscript-fill-string-function justify)) - ;; Decorators - ((equal (char-after (save-excursion - (godot-gdscript-nav-beginning-of-statement))) ?@) - (funcall godot-gdscript-fill-decorator-function justify)) - ;; Parens - ((or (godot-gdscript-syntax-context 'paren) - (looking-at (godot-gdscript-rx open-paren)) - (save-excursion - (skip-syntax-forward "^(" (line-end-position)) - (looking-at (godot-gdscript-rx open-paren)))) - (funcall godot-gdscript-fill-paren-function justify)) - (t t)))) - -(defun godot-gdscript-fill-comment (&optional justify) - "Comment fill function for `godot-gdscript-fill-paragraph'. -JUSTIFY should be used (if applicable) as in `fill-paragraph'." - (fill-comment-paragraph justify)) - -(defun godot-gdscript-fill-string (&optional justify) - "String fill function for `godot-gdscript-fill-paragraph'. -JUSTIFY should be used (if applicable) as in `fill-paragraph'." - (let* ((str-start-pos - (set-marker - (make-marker) - (or (godot-gdscript-syntax-context 'string) - (and (equal (string-to-syntax "|") - (syntax-after (point))) - (point))))) - (num-quotes (godot-gdscript-syntax-count-quotes - (char-after str-start-pos) str-start-pos)) - (str-end-pos - (save-excursion - (goto-char (+ str-start-pos num-quotes)) - (or (re-search-forward (rx (syntax string-delimiter)) nil t) - (goto-char (point-max))) - (point-marker))) - (multi-line-p - ;; Docstring styles may vary for oneliners and multi-liners. - (> (count-matches "\n" str-start-pos str-end-pos) 0)) - (delimiters-style - (pcase godot-gdscript-fill-docstring-style - ;; delimiters-style is a cons cell with the form - ;; (START-NEWLINES . END-NEWLINES). When any of the sexps - ;; is NIL means to not add any newlines for start or end - ;; of docstring. See `godot-gdscript-fill-docstring-style' for a - ;; graphic idea of each style. - (`django (cons 1 1)) - (`onetwo (and multi-line-p (cons 1 2))) - (`pep-257 (and multi-line-p (cons nil 2))) - (`pep-257-nn (and multi-line-p (cons nil 1))) - (`symmetric (and multi-line-p (cons 1 1))))) - (docstring-p (save-excursion - ;; Consider docstrings those strings which - ;; start on a line by themselves. - (godot-gdscript-nav-beginning-of-statement) - (and (= (point) str-start-pos)))) - (fill-paragraph-function)) - (save-restriction - (narrow-to-region str-start-pos str-end-pos) - (fill-paragraph justify)) - (save-excursion - (when (and docstring-p godot-gdscript-fill-docstring-style) - ;; Add the number of newlines indicated by the selected style - ;; at the start of the docstring. - (goto-char (+ str-start-pos num-quotes)) - (delete-region (point) (progn - (skip-syntax-forward "> ") - (point))) - (and (car delimiters-style) - (or (newline (car delimiters-style)) t) - ;; Indent only if a newline is added. - (indent-according-to-mode)) - ;; Add the number of newlines indicated by the selected style - ;; at the end of the docstring. - (goto-char (if (not (= str-end-pos (point-max))) - (- str-end-pos num-quotes) - str-end-pos)) - (delete-region (point) (progn - (skip-syntax-backward "> ") - (point))) - (and (cdr delimiters-style) - ;; Add newlines only if string ends. - (not (= str-end-pos (point-max))) - (or (newline (cdr delimiters-style)) t) - ;; Again indent only if a newline is added. - (indent-according-to-mode))))) t) - -(defun godot-gdscript-fill-decorator (&optional _justify) - "Decorator fill function for `godot-gdscript-fill-paragraph'. -JUSTIFY should be used (if applicable) as in `fill-paragraph'." - t) - -(defun godot-gdscript-fill-paren (&optional justify) - "Paren fill function for `godot-gdscript-fill-paragraph'. -JUSTIFY should be used (if applicable) as in `fill-paragraph'." - (save-restriction - (narrow-to-region (progn - (while (godot-gdscript-syntax-context 'paren) - (goto-char (1- (point-marker)))) - (point-marker) - (line-beginning-position)) - (progn - (when (not (godot-gdscript-syntax-context 'paren)) - (end-of-line) - (when (not (godot-gdscript-syntax-context 'paren)) - (skip-syntax-backward "^)"))) - (while (and (godot-gdscript-syntax-context 'paren) - (not (eobp))) - (goto-char (1+ (point-marker)))) - (point-marker))) - (let ((paragraph-start "\f\\|[ \t]*$") - (paragraph-separate ",") - (fill-paragraph-function)) - (goto-char (point-min)) - (fill-paragraph justify)) - (while (not (eobp)) - (forward-line 1) - (godot-gdscript-indent-line) - (goto-char (line-end-position)))) - t) - -;;; Skeletons - -(defcustom godot-gdscript-skeleton-autoinsert nil - "Non-nil means template skeletons will be automagically inserted. -This happens when pressing \"if\", for example, to prompt for -the if condition." - :type 'boolean - :group 'godot-gdscript - :safe 'booleanp) - -(define-obsolete-variable-alias - 'godot-gdscript-use-skeletons 'godot-gdscript-skeleton-autoinsert "24.3") - -(defvar godot-gdscript-skeleton-available '() - "Internal list of available skeletons.") - -(define-abbrev-table 'godot-gdscript-mode-skeleton-abbrev-table () - "Abbrev table for Godot-Gdscript mode skeletons." - :case-fixed t - ;; Allow / inside abbrevs. - :regexp "\\(?:^\\|[^/]\\)\\<\\([[:word:]/]+\\)\\W*" - ;; Only expand in code. - :enable-function (lambda () - (and - (not (godot-gdscript-syntax-comment-or-string-p)) - godot-gdscript-skeleton-autoinsert))) - -(defmacro godot-gdscript-skeleton-define (name doc &rest skel) - "Define a skeleton using NAME DOC and SKEL. -The skeleton will be bound to godot-gdscript-skeleton-NAME and will -be added to `godot-gdscript-mode-skeleton-abbrev-table'." - (declare (indent 2)) - (let* ((name (symbol-name name)) - (function-name (intern (concat "godot-gdscript-skeleton-" name)))) - `(progn - (define-abbrev godot-gdscript-mode-skeleton-abbrev-table - ,name "" ',function-name :system t) - (setq godot-gdscript-skeleton-available - (cons ',function-name godot-gdscript-skeleton-available)) - (define-skeleton ,function-name - ,(or doc - (format "Insert %s statement." name)) - ,@skel)))) - -(define-abbrev-table 'godot-gdscript-mode-abbrev-table () - "Abbrev table for Godot-Gdscript mode." - :parents (list godot-gdscript-mode-skeleton-abbrev-table)) - -(defmacro godot-gdscript-define-auxiliary-skeleton (name &optional doc &rest skel) - "Define a auxiliary skeleton using NAME DOC and SKEL. -The skeleton will be bound to godot-gdscript-skeleton-NAME." - (declare (indent 2)) - (let* ((name (symbol-name name)) - (function-name (intern (concat "godot-gdscript-skeleton--" name))) - (msg (funcall (if (fboundp 'format-message) #'format-message #'format) - "Add `%s' clause? " name))) - (when (not skel) - (setq skel - `(< ,(format "%s:" name) \n \n - > _ \n))) - `(define-skeleton ,function-name - ,(or doc - (format "Auxiliary skeleton for %s statement." name)) - nil - (unless (y-or-n-p ,msg) - (signal 'quit t)) - ,@skel))) - -(godot-gdscript-define-auxiliary-skeleton else) - -(godot-gdscript-define-auxiliary-skeleton except) - -(godot-gdscript-define-auxiliary-skeleton finally) - -(godot-gdscript-skeleton-define if nil - "Condition: " - "if " str ":" \n - _ \n - ("other condition, %s: " - < - "elif " str ":" \n - > _ \n nil) - '(godot-gdscript-skeleton--else) | ^) - -(godot-gdscript-skeleton-define while nil - "Condition: " - "while " str ":" \n - > _ \n - '(godot-gdscript-skeleton--else) | ^) - -(godot-gdscript-skeleton-define for nil - "Iteration spec: " - "for " str ":" \n - > _ \n - '(godot-gdscript-skeleton--else) | ^) - -(godot-gdscript-skeleton-define try nil - nil - "try:" \n - > _ \n - ("Exception, %s: " - < - "except " str ":" \n - > _ \n nil) - resume: - '(godot-gdscript-skeleton--except) - '(godot-gdscript-skeleton--else) - '(godot-gdscript-skeleton--finally) | ^) - -(godot-gdscript-skeleton-define def nil - "Function name: " - "def " str "(" ("Parameter, %s: " - (unless (equal ?\( (char-before)) ", ") - str) "):" \n - "\"\"\"" - "\"\"\"" \n - > _ \n) - -(godot-gdscript-skeleton-define class nil - "Class name: " - "class " str "(" ("Inheritance, %s: " - (unless (equal ?\( (char-before)) ", ") - str) - & ")" | -2 - ":" \n - "\"\"\"" - "\"\"\"" \n - > _ \n) - -(defun godot-gdscript-skeleton-add-menu-items () - "Add menu items to Godot-Gdscript->Skeletons menu." - (let ((skeletons (sort godot-gdscript-skeleton-available 'string<))) - (dolist (skeleton skeletons) - (easy-menu-add-item - nil '("Godot-Gdscript" "Skeletons") - `[,(format - "Insert %s" (nth 2 (split-string (symbol-name skeleton) "-"))) - ,skeleton t])))) - -;;; FFAP - -(defcustom godot-gdscript-ffap-setup-code - "def __FFAP_get_module_path(module): - try: - import os - path = __import__(module).__file__ - if path[-4:] == '.pyc' and os.path.exists(path[0:-1]): - path = path[:-1] - return path - except: - return ''" - "Godot-Gdscript code to get a module path." - :type 'string - :group 'godot-gdscript) - -(defcustom godot-gdscript-ffap-string-code - "__FFAP_get_module_path('''%s''')\n" - "Godot-Gdscript code used to get a string with the path of a module." - :type 'string - :group 'godot-gdscript) - -(defun godot-gdscript-ffap-module-path (module) - "Function for `ffap-alist' to return path for MODULE." - (let ((process (or - (and (derived-mode-p 'inferior-godot-gdscript-mode) - (get-buffer-process (current-buffer))) - (godot-gdscript-shell-get-process)))) - (if (not process) - nil - (let ((module-file - (godot-gdscript-shell-send-string-no-output - (format godot-gdscript-ffap-string-code module) process))) - (when module-file - (substring-no-properties module-file 1 -1)))))) - -(eval-after-load "ffap" - '(progn - (push '(godot-gdscript-mode . godot-gdscript-ffap-module-path) ffap-alist) - (push '(inferior-godot-gdscript-mode . godot-gdscript-ffap-module-path) ffap-alist))) - -(defun godot-gdscript-build-shell-command (&optional path) - "Build base shell command to run Godot Engine with the -project's base PATH. If PATH is not provided, try to find it -using the current file's directory as starting point." - (let* ((project-path (or path (godot-gdscript-find-project-configuration)))) - (concat godot-gdscript-shell-interpreter " --path " project-path))) - -(defun godot-gdscript-run-godot-editor () - "Run Godot Engine Editor." - (interactive) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " -e"))) - -(defun godot-gdscript-run-project-in-godot () - "Run the current project in Godot Engine." - (interactive) - (let* ((project-path (godot-gdscript-find-project-configuration))) - (godot-gdscript-run-script - (godot-gdscript-build-shell-command)))) - -(defun godot-gdscript-run-project-in-godot-debug-mode () - "Run the current project in Godot Engine." - (interactive) - (let* ((project-path (godot-gdscript-find-project-configuration))) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " -d")))) - -(defun godot-gdscript-run-current-scene-in-godot () - "Run the current script file in Godot Engine." - (interactive) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " " (file-name-sans-extension (file-relative-name buffer-file-name)) ".tscn"))) - -(defun godot-gdscript-run-current-scene-in-godot-debug-mode () - "Run the current script file in Godot Engine." - (interactive) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " -d " (file-name-sans-extension (file-relative-name buffer-file-name)) ".tscn"))) - -(defun godot-gdscript-edit-current-scene-in-godot () - "Run the current script file in Godot Engine." - (interactive) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " -e " (file-name-sans-extension (file-relative-name buffer-file-name)) ".tscn"))) - -(defun godot-gdscript-run-current-script-in-godot () - "Run the current script file in Godot Engine. - -For this to work, the script must inherit either from -\"SceneTree\" or \"MainLoop\"." - (interactive) - (godot-gdscript-run-script - (concat (godot-gdscript-build-shell-command) " -s " (file-relative-name buffer-file-name)))) - -(defun godot-gdscript-find-project-configuration (&optional path) - "Return the path where Godot's configuration File (\"project.godot\") is stored. - -If PATH is given, starts searching by it. Otherwise, the search -starts by the current buffer path." - ;; TODO: Handle error when project file does not exist. - ;; TODO: This is now duplicated in Company-Godot-GDScript. - (let ((base-path (or path default-directory))) - (locate-dominating-file base-path - (lambda (parent) - (directory-files parent t "project.godot"))))) - -(defun godot-gdscript-get-project-name () - "Retrieves the project name from Godot's configuration file." - (with-temp-buffer - (insert-file-contents (concat (godot-gdscript-find-project-configuration) "project.godot")) - (goto-char (point-min)) - (if (re-search-forward "config/name=\"\\([^\"]*\\)\"" nil t) - (match-string 1) - (error "Could not find the name of the project")))) - -;;; Code check - -(defcustom godot-gdscript-check-command - "pyflakes" - "Command used to check a Godot-Gdscript file." - :type 'string - :group 'godot-gdscript) - -(defcustom godot-gdscript-check-buffer-name - "*Godot-Gdscript check: %s*" - "Buffer name used for check commands." - :type 'string - :group 'godot-gdscript) - -(defvar godot-gdscript-check-custom-command nil - "Internal use.") - -(defun godot-gdscript-check (command) - "Check a Godot-Gdscript file (default current buffer's file). -Runs COMMAND, a shell command, as if by `compile'. -See `godot-gdscript-check-command' for the default." - (interactive - (list (read-string "Check command: " - (or godot-gdscript-check-custom-command - (concat godot-gdscript-check-command " " - (shell-quote-argument - (or - (let ((name (buffer-file-name))) - (and name - (file-name-nondirectory name))) - ""))))))) - (setq godot-gdscript-check-custom-command command) - (save-some-buffers (not compilation-ask-about-save) nil) - (let ((process-environment (godot-gdscript-shell-calculate-process-environment)) - (exec-path (godot-gdscript-shell-calculate-exec-path))) - (compilation-start command nil - (lambda (_modename) - (format godot-gdscript-check-buffer-name command))))) - -;;; Imenu - -(defvar godot-gdscript-imenu-format-item-label-function - 'godot-gdscript-imenu-format-item-label - "Imenu function used to format an item label. -It must be a function with two arguments: TYPE and NAME.") - -(defvar godot-gdscript-imenu-format-parent-item-label-function - 'godot-gdscript-imenu-format-parent-item-label - "Imenu function used to format a parent item label. -It must be a function with two arguments: TYPE and NAME.") - -(defvar godot-gdscript-imenu-format-parent-item-jump-label-function - 'godot-gdscript-imenu-format-parent-item-jump-label - "Imenu function used to format a parent jump item label. -It must be a function with two arguments: TYPE and NAME.") - -(defun godot-gdscript-imenu-format-item-label (type name) - "Return Imenu label for single node using TYPE and NAME." - (format "%s (%s)" name type)) - -(defun godot-gdscript-imenu-format-parent-item-label (type name) - "Return Imenu label for parent node using TYPE and NAME." - (format "%s..." (godot-gdscript-imenu-format-item-label type name))) - -(defun godot-gdscript-imenu-format-parent-item-jump-label (type _name) - "Return Imenu label for parent node jump using TYPE and NAME." - (if (string= type "class") - "*class definition*" - "*function definition*")) - -(defun godot-gdscript-imenu--put-parent (type name pos tree) - "Add the parent with TYPE, NAME and POS to TREE." - (let ((label - (funcall godot-gdscript-imenu-format-item-label-function type name)) - (jump-label - (funcall godot-gdscript-imenu-format-parent-item-jump-label-function type name))) - (if (not tree) - (cons label pos) - (cons label (cons (cons jump-label pos) tree))))) - -(defun godot-gdscript-imenu--build-tree (&optional min-indent prev-indent tree) - "Recursively build the tree of nested definitions of a node. -Arguments MIN-INDENT, PREV-INDENT and TREE are internal and should -not be passed explicitly unless you know what you are doing." - (setq min-indent (or min-indent 0) - prev-indent (or prev-indent godot-gdscript-indent-offset)) - (let* ((pos (godot-gdscript-nav-backward-defun)) - (type) - (name (when (and pos (looking-at godot-gdscript-nav-beginning-of-defun-regexp)) - (let ((split (split-string (match-string-no-properties 0)))) - (setq type (car split)) - (cadr split)))) - (label (when name - (funcall godot-gdscript-imenu-format-item-label-function type name))) - (indent (current-indentation)) - (children-indent-limit (+ godot-gdscript-indent-offset min-indent))) - (cond ((not pos) - ;; Nothing found, probably near to bobp. - nil) - ((<= indent min-indent) - ;; The current indentation points that this is a parent - ;; node, add it to the tree and stop recursing. - (godot-gdscript-imenu--put-parent type name pos tree)) - (t - (godot-gdscript-imenu--build-tree - min-indent - indent - (if (<= indent children-indent-limit) - ;; This lies within the children indent offset range, - ;; so it's a normal child of its parent (i.e., not - ;; a child of a child). - (cons (cons label pos) tree) - ;; Oh no, a child of a child?! Fear not, we - ;; know how to roll. We recursively parse these by - ;; swapping prev-indent and min-indent plus adding this - ;; newly found item to a fresh subtree. This works, I - ;; promise. - (cons - (godot-gdscript-imenu--build-tree - prev-indent indent (list (cons label pos))) - tree))))))) - -(defun godot-gdscript-imenu-create-index () - "Return tree Imenu alist for the current Godot-Gdscript buffer. -Change `godot-gdscript-imenu-format-item-label-function', -`godot-gdscript-imenu-format-parent-item-label-function', -`godot-gdscript-imenu-format-parent-item-jump-label-function' to -customize how labels are formatted." - (goto-char (point-max)) - (let ((index) - (tree)) - (while (setq tree (godot-gdscript-imenu--build-tree)) - (setq index (cons tree index))) - index)) - -(defun godot-gdscript-imenu-create-flat-index (&optional alist prefix) - "Return flat outline of the current Godot-Gdscript buffer for Imenu. -Optional argument ALIST is the tree to be flattened; when nil -`godot-gdscript-imenu-build-index' is used with -`godot-gdscript-imenu-format-parent-item-jump-label-function' -`godot-gdscript-imenu-format-parent-item-label-function' -`godot-gdscript-imenu-format-item-label-function' set to - (lambda (type name) name) -Optional argument PREFIX is used in recursive calls and should -not be passed explicitly. - -Converts this: - - ((\"Foo\" . 103) - (\"Bar\" . 138) - (\"decorator\" - (\"decorator\" . 173) - (\"wrap\" - (\"wrap\" . 353) - (\"wrapped_f\" . 393)))) - -To this: - - ((\"Foo\" . 103) - (\"Bar\" . 138) - (\"decorator\" . 173) - (\"decorator.wrap\" . 353) - (\"decorator.wrapped_f\" . 393))" - ;; Inspired by imenu--flatten-index-alist removed in revno 21853. - (apply - 'nconc - (mapcar - (lambda (item) - (let ((name (if prefix - (concat prefix "." (car item)) - (car item))) - (pos (cdr item))) - (cond ((or (numberp pos) (markerp pos)) - (list (cons name pos))) - ((listp pos) - (cons - (cons name (cdar pos)) - (godot-gdscript-imenu-create-flat-index (cddr item) name)))))) - (or alist - (let* ((fn (lambda (_type name) name)) - (godot-gdscript-imenu-format-item-label-function fn) - (godot-gdscript-imenu-format-parent-item-label-function fn) - (godot-gdscript-imenu-format-parent-item-jump-label-function fn)) - (godot-gdscript-imenu-create-index)))))) - -;;; Misc helpers - -(defun godot-gdscript-info-current-defun (&optional include-type) - "Return name of surrounding function with Gdscript compatible dotty syntax. -Optional argument INCLUDE-TYPE indicates to include the type of the defun. -This function can be used as the value of `add-log-current-defun-function' -since it returns nil if point is not inside a defun." - (save-restriction - (widen) - (save-excursion - (end-of-line 1) - (let ((names) - (starting-indentation (current-indentation)) - (starting-pos (point)) - (first-run t) - (last-indent) - (type)) - (catch 'exit - (while (godot-gdscript-nav-beginning-of-defun 1) - (when (save-match-data - (and - (or (not last-indent) - (< (current-indentation) last-indent)) - (or - (and first-run - (save-excursion - ;; If this is the first run, we may add - ;; the current defun at point. - (setq first-run nil) - (goto-char starting-pos) - (godot-gdscript-nav-beginning-of-statement) - (beginning-of-line 1) - (looking-at-p - godot-gdscript-nav-beginning-of-defun-regexp))) - (< starting-pos - (save-excursion - (let ((min-indent - (+ (current-indentation) - godot-gdscript-indent-offset))) - (if (< starting-indentation min-indent) - ;; If the starting indentation is not - ;; within the min defun indent make the - ;; check fail. - starting-pos - ;; Else go to the end of defun and add - ;; up the current indentation to the - ;; ending position. - (godot-gdscript-nav-end-of-defun) - (+ (point) - (if (>= (current-indentation) min-indent) - (1+ (current-indentation)) - 0))))))))) - (save-match-data (setq last-indent (current-indentation))) - (if (or (not include-type) type) - (setq names (cons (match-string-no-properties 1) names)) - (let ((match (split-string (match-string-no-properties 0)))) - (setq type (car match)) - (setq names (cons (cadr match) names))))) - ;; Stop searching ASAP. - (and (= (current-indentation) 0) (throw 'exit t)))) - (and names - (concat (and type (format "%s " type)) - (mapconcat 'identity names "."))))))) - -(defun godot-gdscript-info-current-symbol (&optional replace-self) - "Return current symbol using dotty syntax. -With optional argument REPLACE-SELF convert \"self\" to current -parent defun name." - (let ((name - (and (not (godot-gdscript-syntax-comment-or-string-p)) - (with-syntax-table godot-gdscript-dotty-syntax-table - (let ((sym (symbol-at-point))) - (and sym - (substring-no-properties (symbol-name sym)))))))) - (when name - (if (not replace-self) - name - (let ((current-defun (godot-gdscript-info-current-defun))) - (if (not current-defun) - name - (replace-regexp-in-string - (godot-gdscript-rx line-start word-start "self" word-end ?.) - (concat - (mapconcat 'identity - (butlast (split-string current-defun "\\.")) - ".") ".") - name))))))) - -(defun godot-gdscript-info-statement-starts-block-p () - "Return non-nil if current statement opens a block." - (save-excursion - (godot-gdscript-nav-beginning-of-statement) - (looking-at (godot-gdscript-rx block-start)))) - -(defun godot-gdscript-info-statement-ends-block-p () - "Return non-nil if point is at end of block." - (let ((end-of-block-pos (save-excursion - (godot-gdscript-nav-end-of-block))) - (end-of-statement-pos (save-excursion - (godot-gdscript-nav-end-of-statement)))) - (and end-of-block-pos end-of-statement-pos - (= end-of-block-pos end-of-statement-pos)))) - -(defun godot-gdscript-info-beginning-of-statement-p () - "Return non-nil if point is at beginning of statement." - (= (point) (save-excursion - (godot-gdscript-nav-beginning-of-statement) - (point)))) - -(defun godot-gdscript-info-end-of-statement-p () - "Return non-nil if point is at end of statement." - (= (point) (save-excursion - (godot-gdscript-nav-end-of-statement) - (point)))) - -(defun godot-gdscript-info-beginning-of-block-p () - "Return non-nil if point is at beginning of block." - (and (godot-gdscript-info-beginning-of-statement-p) - (godot-gdscript-info-statement-starts-block-p))) - -(defun godot-gdscript-info-end-of-block-p () - "Return non-nil if point is at end of block." - (and (godot-gdscript-info-end-of-statement-p) - (godot-gdscript-info-statement-ends-block-p))) - -(define-obsolete-function-alias - 'godot-gdscript-info-closing-block - 'godot-gdscript-info-dedenter-opening-block-position "24.4") - -(defun godot-gdscript-info-dedenter-opening-block-position () - "Return the point of the closest block the current line closes. -Returns nil if point is not on a dedenter statement or no opening -block can be detected. The latter case meaning current file is -likely an invalid godot-gdscript file." - (let ((positions (godot-gdscript-info-dedenter-opening-block-positions)) - (indentation (current-indentation)) - (position)) - (while (and (not position) - positions) - (save-excursion - (goto-char (car positions)) - (if (<= (current-indentation) indentation) - (setq position (car positions)) - (setq positions (cdr positions))))) - position)) - -(defun godot-gdscript-info-dedenter-opening-block-positions () - "Return points of blocks the current line may close sorted by closer. -Returns nil if point is not on a dedenter statement or no opening -block can be detected. The latter case meaning current file is -likely an invalid godot-gdscript file." - (save-excursion - (let ((dedenter-pos (godot-gdscript-info-dedenter-statement-p))) - (when dedenter-pos - (goto-char dedenter-pos) - (let* ((pairs '(("elif" "elif" "if") - ("else" "if" "elif" "except" "for" "while") - ("except" "except" "try") - ("finally" "else" "except" "try"))) - (dedenter (match-string-no-properties 0)) - (possible-opening-blocks (cdr (assoc-string dedenter pairs))) - (collected-indentations) - (opening-blocks)) - (catch 'exit - (while (godot-gdscript-nav--syntactically - (lambda () - (re-search-backward (godot-gdscript-rx block-start) nil t)) - #'<) - (let ((indentation (current-indentation))) - (when (and (not (memq indentation collected-indentations)) - (or (not collected-indentations) - (< indentation (apply #'min collected-indentations)))) - (setq collected-indentations - (cons indentation collected-indentations)) - (when (member (match-string-no-properties 0) - possible-opening-blocks) - (setq opening-blocks (cons (point) opening-blocks)))) - (when (zerop indentation) - (throw 'exit nil))))) - ;; sort by closer - (nreverse opening-blocks)))))) - -(define-obsolete-function-alias - 'godot-gdscript-info-closing-block-message - 'godot-gdscript-info-dedenter-opening-block-message "24.4") - -(defun godot-gdscript-info-dedenter-opening-block-message () - "Message the first line of the block the current statement closes." - (let ((point (godot-gdscript-info-dedenter-opening-block-position))) - (when point - (save-restriction - (widen) - (message "Closes %s" (save-excursion - (goto-char point) - (buffer-substring - (point) (line-end-position)))))))) - -(defun godot-gdscript-info-dedenter-statement-p () - "Return point if current statement is a dedenter. -Sets `match-data' to the keyword that starts the dedenter -statement." - (save-excursion - (godot-gdscript-nav-beginning-of-statement) - (when (and (not (godot-gdscript-syntax-context-type)) - (looking-at (godot-gdscript-rx dedenter))) - (point)))) - -(defun godot-gdscript-info-line-ends-backslash-p (&optional line-number) - "Return non-nil if current line ends with backslash. -With optional argument LINE-NUMBER, check that line instead." - (save-excursion - (save-restriction - (widen) - (when line-number - (godot-gdscript-util-goto-line line-number)) - (while (and (not (eobp)) - (goto-char (line-end-position)) - (godot-gdscript-syntax-context 'paren) - (not (equal (char-before (point)) ?\\))) - (forward-line 1)) - (when (equal (char-before) ?\\) - (point-marker))))) - -(defun godot-gdscript-info-beginning-of-backslash (&optional line-number) - "Return the point where the backslashed line start. -Optional argument LINE-NUMBER forces the line number to check against." - (save-excursion - (save-restriction - (widen) - (when line-number - (godot-gdscript-util-goto-line line-number)) - (when (godot-gdscript-info-line-ends-backslash-p) - (while (save-excursion - (goto-char (line-beginning-position)) - (godot-gdscript-syntax-context 'paren)) - (forward-line -1)) - (back-to-indentation) - (point-marker))))) - -(defun godot-gdscript-info-continuation-line-p () - "Check if current line is continuation of another. -When current line is continuation of another return the point -where the continued line ends." - (save-excursion - (save-restriction - (widen) - (let* ((context-type (progn - (back-to-indentation) - (godot-gdscript-syntax-context-type))) - (line-start (line-number-at-pos)) - (context-start (when context-type - (godot-gdscript-syntax-context context-type)))) - (cond ((equal context-type 'paren) - ;; Lines inside a paren are always a continuation line - ;; (except the first one). - (godot-gdscript-util-forward-comment -1) - (point-marker)) - ((member context-type '(string comment)) - ;; move forward an roll again - (goto-char context-start) - (godot-gdscript-util-forward-comment) - (godot-gdscript-info-continuation-line-p)) - (t - ;; Not within a paren, string or comment, the only way - ;; we are dealing with a continuation line is that - ;; previous line contains a backslash, and this can - ;; only be the previous line from current - (back-to-indentation) - (godot-gdscript-util-forward-comment -1) - (when (and (equal (1- line-start) (line-number-at-pos)) - (godot-gdscript-info-line-ends-backslash-p)) - (point-marker)))))))) - -(defun godot-gdscript-info-block-continuation-line-p () - "Return non-nil if current line is a continuation of a block." - (save-excursion - (when (godot-gdscript-info-continuation-line-p) - (forward-line -1) - (back-to-indentation) - (when (looking-at (godot-gdscript-rx block-start)) - (point-marker))))) - -(defun godot-gdscript-info-assignment-continuation-line-p () - "Check if current line is a continuation of an assignment. -When current line is continuation of another with an assignment -return the point of the first non-blank character after the -operator." - (save-excursion - (when (godot-gdscript-info-continuation-line-p) - (forward-line -1) - (back-to-indentation) - (when (and (not (looking-at (godot-gdscript-rx block-start))) - (and (re-search-forward (godot-gdscript-rx not-simple-operator - assignment-operator - not-simple-operator) - (line-end-position) t) - (not (godot-gdscript-syntax-context-type)))) - (skip-syntax-forward "\s") - (point-marker))))) - -(defun godot-gdscript-info-looking-at-beginning-of-defun (&optional syntax-ppss) - "Check if point is at `beginning-of-defun' using SYNTAX-PPSS." - (and (not (godot-gdscript-syntax-context-type (or syntax-ppss (syntax-ppss)))) - (save-excursion - (beginning-of-line 1) - (looking-at godot-gdscript-nav-beginning-of-defun-regexp)))) - -(defun godot-gdscript-info-current-line-comment-p () - "Return non-nil if current line is a comment line." - (char-equal - (or (char-after (+ (line-beginning-position) (current-indentation))) ?_) - ?#)) - -(defun godot-gdscript-info-current-line-empty-p () - "Return non-nil if current line is empty, ignoring whitespace." - (save-excursion - (beginning-of-line 1) - (looking-at - (godot-gdscript-rx line-start (* whitespace) - (group (* not-newline)) - (* whitespace) line-end)) - (string-equal "" (match-string-no-properties 1)))) - -(defun godot-gdscript-info-encoding-from-cookie () - "Detect current buffer's encoding from its coding cookie. -Returns the encoding as a symbol." - (let ((first-two-lines - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (forward-line 2) - (buffer-substring-no-properties - (point) - (point-min)))))) - (when (string-match (godot-gdscript-rx coding-cookie) first-two-lines) - (intern (match-string-no-properties 1 first-two-lines))))) - -(defun godot-gdscript-info-encoding () - "Return encoding for file. -Try `godot-gdscript-info-encoding-from-cookie', if none is found then -default to utf-8." - ;; If no encoding is defined, then it's safe to use UTF-8: Godot-Gdscript 2 - ;; uses ASCII as default while Godot-Gdscript 3 uses UTF-8. This means that - ;; in the worst case scenario godot-gdscript.el will make things work for - ;; Godot-Gdscript 2 files with unicode data and no encoding defined. - (or (godot-gdscript-info-encoding-from-cookie) - 'utf-8)) - - -;;; Utility functions - -(defun godot-gdscript-util-goto-line (line-number) - "Move point to LINE-NUMBER." - (goto-char (point-min)) - (forward-line (1- line-number))) - -;; Stolen from org-mode -(defun godot-gdscript-util-clone-local-variables (from-buffer &optional regexp) - "Clone local variables from FROM-BUFFER. -Optional argument REGEXP selects variables to clone and defaults -to \"^godot-gdscript-\"." - (mapc - (lambda (pair) - (and (symbolp (car pair)) - (string-match (or regexp "^godot-gdscript-") - (symbol-name (car pair))) - (set (make-local-variable (car pair)) - (cdr pair)))) - (buffer-local-variables from-buffer))) - -(defun godot-gdscript-util-forward-comment (&optional direction) - "Godot-Gdscript mode specific version of `forward-comment'. -Optional argument DIRECTION defines the direction to move to." - (let ((comment-start (godot-gdscript-syntax-context 'comment)) - (factor (if (< (or direction 0) 0) - -99999 - 99999))) - (when comment-start - (goto-char comment-start)) - (forward-comment factor))) - -(defun godot-gdscript-util-popn (lst n) - "Return LST first N elements. -N should be an integer, when negative its opposite is used. -When N is bigger than the length of LST, the list is -returned as is." - (let* ((n (min (abs n))) - (len (length lst)) - (acc)) - (if (> n len) - lst - (while (< 0 n) - (setq acc (cons (car lst) acc) - lst (cdr lst) - n (1- n))) - (reverse acc)))) - -(defun godot-gdscript-util-strip-string (string) - "Strip STRING whitespace and newlines from end and beginning." - (replace-regexp-in-string - (rx (or (: string-start (* (any whitespace ?\r ?\n))) - (: (* (any whitespace ?\r ?\n)) string-end))) - "" - string)) - -(defun godot-gdscript-util-valid-regexp-p (regexp) - "Return non-nil if REGEXP is valid." - (ignore-errors (string-match regexp "") t)) - - -(defun godot-gdscript-electric-pair-string-delimiter () - "Identify the delimiter for a string." - (when (and electric-pair-mode - (memq last-command-event '(?\" ?\')) - (let ((count 0)) - (while (eq (char-before (- (point) count)) last-command-event) - (cl-incf count)) - (= count 3)) - (eq (char-after) last-command-event)) - (save-excursion (insert (make-string 2 last-command-event))))) - -;;;###autoload -(define-derived-mode godot-gdscript-mode prog-mode "Godot-Gdscript" - "Major mode for editing Godot Engine GDScript files. - -\\{godot-gdscript-mode-map}" - (set (make-local-variable 'tab-width) 4) - (set (make-local-variable 'indent-tabs-mode) t) - - (set (make-local-variable 'comment-start) "# ") - (set (make-local-variable 'comment-start-skip) "#+\\s-*") - - (set (make-local-variable 'parse-sexp-lookup-properties) t) - (set (make-local-variable 'parse-sexp-ignore-comments) t) - - (set (make-local-variable 'forward-sexp-function) - 'godot-gdscript-nav-forward-sexp) - - (set (make-local-variable 'font-lock-defaults) - '(godot-gdscript-font-lock-keywords nil nil nil nil)) - - (set (make-local-variable 'syntax-propertize-function) - godot-gdscript-syntax-propertize-function) - - (set (make-local-variable 'indent-line-function) - #'godot-gdscript-indent-line-function) - (set (make-local-variable 'indent-region-function) #'godot-gdscript-indent-region) - ;; Because indentation is not redundant, we cannot safely reindent code. - (setq-local electric-indent-inhibit t) - (setq-local electric-indent-chars (cons ?: electric-indent-chars)) - - ;; Add """ ... """ pairing to electric-pair-mode. - (add-hook 'post-self-insert-hook - #'godot-gdscript-electric-pair-string-delimiter 'append t) - - (set (make-local-variable 'paragraph-start) "\\s-*$") - (set (make-local-variable 'fill-paragraph-function) - #'godot-gdscript-fill-paragraph) - - (set (make-local-variable 'beginning-of-defun-function) - #'godot-gdscript-nav-beginning-of-defun) - (set (make-local-variable 'end-of-defun-function) - #'godot-gdscript-nav-end-of-defun) - - (add-hook 'completion-at-point-functions - #'godot-gdscript-completion-complete-at-point nil 'local) - - (add-hook 'post-self-insert-hook - #'godot-gdscript-indent-post-self-insert-function 'append 'local) - - (set (make-local-variable 'imenu-create-index-function) - #'godot-gdscript-imenu-create-index) - - (set (make-local-variable 'add-log-current-defun-function) - #'godot-gdscript-info-current-defun) - - (add-hook 'which-func-functions #'godot-gdscript-info-current-defun nil t) - - (set (make-local-variable 'skeleton-further-elements) - '((abbrev-mode nil) - (< '(backward-delete-char-untabify (min godot-gdscript-indent-offset - (current-column)))) - (^ '(- (1+ (current-indentation)))))) - - (add-to-list 'hs-special-modes-alist - `(godot-gdscript-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#" - ,(lambda (_arg) - (godot-gdscript-nav-end-of-defun)) nil)) - - (set (make-local-variable 'outline-regexp) - (godot-gdscript-rx (* space) block-start)) - (set (make-local-variable 'outline-heading-end-regexp) ":[^\n]*\n") - (set (make-local-variable 'outline-level) - #'(lambda () - "`outline-level' function for Godot-Gdscript mode." - (1+ (/ (current-indentation) godot-gdscript-indent-offset)))) - - (godot-gdscript-skeleton-add-menu-items) - - (make-local-variable 'godot-gdscript-shell-internal-buffer) - - (when godot-gdscript-indent-guess-indent-offset - (godot-gdscript-indent-guess-indent-offset))) - - - -(provide 'extensions/godot/godot-gdscript) - -;; Local Variables: -;; coding: utf-8 -;; indent-tabs-mode: nil -;; End: - -;;; godot-gdscript.el ends here diff --git a/lib/extensions/godot/init.el b/lib/extensions/godot/init.el index eb7d495..f047081 100644 --- a/lib/extensions/godot/init.el +++ b/lib/extensions/godot/init.el @@ -8,18 +8,12 @@ ;; (setq gdscript-tabs-mode t) ;; (setq gdscript-tab-width 2)) -(defun setup-gdscript() - (interactive) - (setq tab-width 2) - (setq indent-tab-mode nil)) ;;;###autoload (defun extensions/godot-initialize () + (add-to-list 'auto-mode-alist '("\\.gd$" . gdscript-mode)) + (add-hook 'gdscript-mode-hook 'lsp)) - (require 'extensions/godot/godot-gdscript) - (add-to-list 'auto-mode-alist '("\\.gd$" . godot-gdscript-mode)) - (add-hook 'godot-gdscript-mode-hook 'setup-gdscript) - (message "Godot Engine extension has been loaded.")) (provide 'extensions/godot/init) ;;; init.el ends here