Merge branch 'main' of ssh://git.annabunches.net:2222/annabunches/config

This commit is contained in:
Anna Wiggins 2023-09-18 13:29:29 -04:00
commit b2c2c6b2a0
2 changed files with 329 additions and 0 deletions

View File

@ -82,3 +82,7 @@
(require 'py-autopep8)
(add-hook 'python-mode-hook 'py-autopep8-enable-on-save)
(add-to-list 'load-path "~/.emacs.d/local/")
(require 'kos-mode)
(setq kos-indent 2)

325
emacs/local/kos-mode.el Executable file
View File

@ -0,0 +1,325 @@
;;; kos-mode.el --- KSP kOS KerboScript major mode
;; Author: Charlie Green
;; URL: https://github.com/charliegreen/kos-mode
;; Version: 0.2.1
;;; Commentary:
;; A major mode for editing KerboScript program files, from the Kerbal Space Program mod
;; kOS. I hope this is useful for someone!
;; TODO:
;; features:
;; * potentially add custom faces for strings/comments
;; * add AGn action group highlighting
;; * add useful interactive commands (eg electric braces)
;; other:
;; * yikes, clean up some of the regexes here....
;; TODO (long-term):
;; * make sure I got the syntax right and included all global functions and variables
;; * add some nice completion thing for completing fields of data structures (eg
;; SHIP:VELOCITY autofills ":SURFACE", which autofills ":MAG", etc)
;; * deal with highlighting function calls vs global variables, especially when they
;; have the same name (eg STAGE)
;; * add an optional auto-formatter
;;; Code:
(defgroup kos-mode ()
"Options for `kos-mode'."
:group 'languages)
(defgroup kos-mode-faces ()
"Faces used by `kos-mode'."
:group 'kos-mode)
(defcustom kos-indent (default-value 'tab-width)
"Basic indent increment for indenting kOS code."
:group 'kos-mode)
(defface kos-keyword-face
'((t :inherit (font-lock-keyword-face)))
"Face for keywords."
:group 'kos-mode-faces)
(defface kos-operator-face
'((t :inherit (font-lock-builtin-face)))
"Face for operators."
:group 'kos-mode-faces)
(defface kos-global-face
'((t :inherit (font-lock-constant-face)))
"Face for globally defined variables."
:group 'kos-mode-faces)
(defface kos-constant-face
'((t :inherit (font-lock-constant-face)))
"Face for constants."
:group 'kos-mode-faces)
(defface kos-function-name-face
'((t :inherit (font-lock-function-name-face)))
"Face for highlighting the names of functions in their definitions."
:group 'kos-mode-faces)
(defmacro kos--opt (keywords)
"Compile a regex matching any of KEYWORDS."
`(eval-when-compile
(regexp-opt ,keywords 'words)))
(eval-and-compile
(defconst kos-keywords
'("add" "all" "at" "batch" "break" "clearscreen" "compile" "copy" "declare"
"delete" "deploy" "do" "do" "edit" "else" "file" "for" "from" "from"
"function" "global" "if" "in" "is" "local" "lock" "log" "off" "on"
"once" "parameter" "preserve" "print" "reboot" "remove" "rename" "run"
"set" "shutdown" "stage" "step" "switch" "then" "to" "toggle" "unlock"
"unset" "until" "volume" "wait" "when" "return" "lazyglobal")))
(eval-and-compile
(defconst kos-globals
'("ship" "target" "hastarget" "heading" "prograde" "retrograde" "facing"
"maxthrust" "velocity" "geoposition" "latitude" "longitude" "up" "north"
"body" "angularmomentum" "angularvel" "angularvelocity" "mass"
"verticalspeed" "groundspeed" "surfacespeed" "airspeed" "altitude"
"apoapsis" "periapsis" "sensors" "srfprograde" "srfretrograde" "obt"
"status" "shipname"
"terminal" "core" "archive" "nextnode" "hasnode" "allnodes"
"liquidfuel" "oxidizer" "electriccharge" "monopropellant" "intakeair"
"solidfuel"
"alt" "eta" "encounter"
"sas" "rcs" "gear" "lights" "brakes" "abort" "legs" "chutes" "chutessafe"
"panels" "radiators" "ladders" "bays" "intakes" "deploydrills" "drills"
"fuelcells" "isru" "ag1" "ag2" "ag3" "ag4" "ag5" "ag6" "ag7" "ag8" "ag9"
"ag10"
"throttle" "steering" "wheelthrottle" "wheelsteering"
"missiontime" "version" "major" "minor" "build" "sessiontime"
"homeconnection" "controlconnection"
"kuniverse" "config" "warp" "warpmode" "mapview" "loaddistance"
"solarprimevector" "addons"
"red" "green" "blue" "yellow" "cyan" "magenta" "purple" "white" "black")))
(eval-and-compile
(defconst kos-functions
'("round" "mod" "abs" "ceiling" "floor" "ln" "log10" "max" "min" "random" "sqrt"
"char" "unchar" "sin" "cos" "tan" "arcsin" "arccos" "arctan" "arctan2"
"list" "rgb" "rgba" "hsv" "hsva"
"clearscreen" "stage" "constant" "profileresult")))
(eval-and-compile
(defconst kos-constants
'("pi" "e" "g" "c" "atmtokpa" "kpatoatm" "degtorad" "radtodeg")))
(defun kos--opt-nomember (keywords)
"Compile a regex matching any of KEYWORDS with no leading colon.
This is the same as `kos--opt', except it won't match any of
KEYWORDS if they are being accessed as a structure member (eg,
for `(kos--opt-nomember '(\"foo\"))`, 'foo' would be highlighted,
but 'bar:foo' would not)."
(concat "\\(?:^\\|[^:]\\)" (kos--opt keywords)))
(defconst kos-font-lock-keywords
;; aren't these regexes beautiful?
`((,(kos--opt-nomember kos-keywords) 1 'kos-keyword-face)
(,(kos--opt-nomember kos-globals) 1 'kos-global-face)
(,(kos--opt-nomember kos-constants) 1 'kos-constant-face)
;; for numbers; have this before operators so decimals are still highlighted
("\\b[[:digit:].]+\\(e[+-]?[:digit:]+\\)?\\b" . 'kos-constant-face)
;; ((rx (any ?+ ?- ?* ?/ ?^ ?( ?))) . 'kos-operator-face) ; arithmetic ops
("\\+\\|-\\|\\*\\|/\\|\\^\\|(\\|)" . 'kos-operator-face) ; arithmetic ops
;; ((rx word-boundary
;; (or "not" "and" "or" "true" "false" "<>" "<=" ">=" "=" ">" "<")
;; word-boundary) 1 'kos-operator-face) ; logical ops
("\\b\\(not\\|and\\|or\\|true\\|false\\|<>\\|<=\\|>=\\|=\\|>\\|<\\)\\b" ; logical ops
1 'kos-operator-face)
;;((rx (any ?{ ?} ?[ ?] ?, ?. ?: ?@)) . 'kos-operator-face) ; other ops
("{\\|}\\|\\[\\|\\]\\|,\\|\\.\\|:\\|@" . 'kos-operator-face) ; other ops
;; highlight function declarations
("\\bfunction\\s-+\\([[:alpha:]_][[:alnum:]_]*\\)" 1 'kos-function-name-face))
"Keyword highlighting specification for `kos-mode'.")
;; (defvar kos-mode-map
;; (let ((map (make-sparse-keymap)))
;; ;(define-key map [foo] 'kos-do-foo)
;; map)
;; "Keymap for `kos-mode'.")
(defvar kos-mode-syntax-table
(let ((st (make-syntax-table)))
(modify-syntax-entry ?/ ". 12" st) ; // starts comments
(modify-syntax-entry ?\n ">" st) ; newline ends comments
st)
"Syntax table for `kos-mode'.")
;; https://web.archive.org/web/20070702002238/http://two-wugs.net/emacs/mode-tutorial.html
(defun kos-indent-line ()
"Indent the current line of kOS code."
(interactive)
(require 'cl-lib)
(let ((not-indented t) cur-indent
(r-bob "^[^\n]*?{[^}]*$") ; beginning of block regex
(r-nl "\\(?:\n\\|\r\n\\|$\\)") ; an actual newline ($ was acting funky)
(ltss nil)) ; lines to start of (unterminated) statement; see `in-unterminated-p'
(cl-flet* ((get-cur-line ()
(save-excursion
(let ((start (progn (beginning-of-line) (point)))
(end (progn (end-of-line) (point))))
(buffer-substring-no-properties start end))))
(back-to-nonblank-line ()
(let ((cont t))
(while cont
(forward-line -1)
(if (or (bobp) (not (looking-at "^\\s-*$")))
(setq cont nil)))))
(set-indent (v &optional not-relative)
(setq cur-indent (if not-relative v
(+ (current-indentation)
(* v kos-indent))))
(setq not-indented nil))
(strip-text
(s) ;; remove strings and comments
(while (string-match
(rx (: ?\" (0+ (or (: ?\\ ?\")
(not (any ?\")))) ?\")) s)
(setq s (replace-match "" t t s)))
(while (string-match (concat "//.*" r-nl) s)
(setq s (replace-match "" t t s))) s)
(in-unterminated-p
()
;; Returns nil if in unterminated statement and the number of
;; lines back to the beginning of the statement otherwise
(setq ltss nil)
(save-excursion
(let ((loopp t) ; whether we should keep searching
(rettp nil) ; whether we'll return a positive value
(count 0) ; number of lines we've processed before
(found-end-p nil) ; whether we've found a terminating line
cur-line) ; the current line we're processing
(while (and loopp (not (bobp)))
(setq cur-line (strip-text (get-cur-line)))
(cond
;; found an unterminated statement
((string-match
(concat "^\\s-*" (kos--opt kos-keywords) "\\b\\s-*[^{.]*$")
cur-line)
(setq loopp nil rettp t))
;; found a block opener
((string-match "^\\s-*{[^}]*$" cur-line)
(setq loopp nil))
;; found a block closer (which counts as terminator,
;; since it presumably has an opener) or a line ending
;; with a dot
((or
(string-match ".*\\.\\s-*$" cur-line)
(string-match "^\\s-*}[^{]*$" cur-line))
;; check if count is zero so we don't indent whitespace
;; past the terminator
(if (and (not found-end-p) (zerop count))
(setq found-end-p t)
(setq loopp nil))))
;; at end of each loop, if we don't want to break, update
;; counter and move POINT to next line to process
(if loopp
(progn
(setq count (1+ count))
(forward-line -1))))
(if (not rettp) nil
(progn
(setq ltss count)
(not (= count 0)))))))
;; for "looking-at-line"
(lal (r) (string-match r (strip-text (get-cur-line)))))
(save-excursion
(beginning-of-line)
(cond ((bobp) (set-indent 0 t)) ; if at beginning of buffer, indent to 0
((lal "^[ \t]*}") ; if closing a block
(progn ; then indent one less than previous line
;; TODO: check if previous line is part of a line continuation
(back-to-nonblank-line)
(cond
((looking-at r-bob) (set-indent 0)) ; if closing empty block, match it
((in-unterminated-p) ; if last line unterminated, indent back two
(set-indent -2))
(t (set-indent -1))))) ; otherwise, indent back one
((lal "^[ \t]*{") ; if opening a block on a blank line
(progn ; then indent the same as last line
(back-to-nonblank-line)
(set-indent 0)))
((in-unterminated-p) ; if line part of unterminated statement
(progn ; then indent one more than beginning
(forward-line (- ltss))
(set-indent +1)))
(t (while not-indented ; else search backwards for clues
(back-to-nonblank-line)
(cond
((bobp) (setq not-indented nil)) ; perhaps we won't find anything
((lal "^[ \t]*}[^{]*$") (set-indent 0)) ; found the end of a block
((lal r-bob) (set-indent +1)))))))) ; found the beginning of a block
(if (not cur-indent) (setq cur-indent 0))
(if (< cur-indent 0) (setq cur-indent 0))
;; now actually indent to cur-indent
(if (save-excursion ; if within indentation
(let ((point (point))
(start (progn (beginning-of-line) (point)))
(end (progn (back-to-indentation) (point))))
(and (<= start point) (<= point end))))
(indent-line-to cur-indent) ; then indent line and move point
(save-excursion (indent-line-to cur-indent))))) ; else just indent line
;;;###autoload
(define-derived-mode kos-mode prog-mode "KerboScript"
"Major mode for editing kOS program files, for the game Kerbal Space Program."
:syntax-table kos-mode-syntax-table
(make-local-variable 'comment-start)
(make-local-variable 'comment-start-skip)
(make-local-variable 'font-lock-defaults)
(make-local-variable 'indent-line-function)
(setq comment-start "// ")
(setq comment-start-skip "//+\\s-*")
(setq font-lock-defaults
'(kos-font-lock-keywords nil t)) ; t makes this case-insensitive
(setq indent-line-function 'kos-indent-line))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.ks\\'" . kos-mode))
(provide 'kos-mode)
;;; kos-mode.el ends here