;;
;; Author: Joe Wreschnig <joe.wreschnig@gmail.com>
;; Package-Version: 20170620
-;; Package-Requires: ((emacs "25") (polymode "20170307") (lua-mode "20151025"))
+;; Package-Requires: ((emacs "25") (polymode "20170307") (lua-mode "20151025") (dash "2.12.0"))
;; URL: https://git.korewanetadesu.com/pico8.git
;; Keywords: convenience
;;
-;; 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 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.
;;; Commentary:
;;
-;; This mode (ab)uses polymode to fit six modes into one buffer, one
-;; of which is "real Lua" text and the other five of which have
-;; diverse strict formatting requirements.
+;; This mode (ab)uses polymode to fit six modes into one buffer, one of
+;; which is "real Lua" text and the other five of which have diverse
+;; strict formatting requirements.
;;
;; It provides keybindings and commands for inter-mode actions.
(require 'polymode)
(require 'lua-mode)
(require 'thingatpt)
+(require 'dash)
+(require 'calc-bin)
(defgroup pico8 nil
:tag "PICO-8"
:group 'languages)
-(defgroup pico8-faces nil
- "Faces for PICO-8 (.p8) cartridge files."
- :tag "PICO-8 Faces"
- :group 'pico8)
-
(defcustom pico8-executable-paths
'("/Applications/PICO-8.app/Contents/MacOS/pico8" ; macOS
"/usr/lib/pico8/pico8" ; PocketCHIP
PICO-8 processes are long-lived with little surprising output, so
their output buffers are killed by default when they exit.
-However, this is not usual behavior in Emacs, and can be
-disabled by setting this to t."
+However, this is not usual behavior in Emacs, and can be disabled
+by setting this to t."
:group 'pico8
:tag "Preserve PICO-8 Output On Exit"
:type 'boolean)
(defcustom pico8-lua-indent-level 1
"Default indentation for PICO-8 Lua mode.
-This overrides `lua-indent-level' in `pico8-lua-mode'.
-`lua-mode''s default indentation is 3, which is both
+This overrides ‘lua-indent-level’ in ‘pico8-lua-mode’.
+‘lua-mode’ uses a default indentation is 3, which is both
idiosyncratic and quite large when viewed in the PICO-8 editor,
where the convention is 1."
:group 'pico8
(defconst pico8-data-characters
(append "0123456789abcdef" nil))
-(defface pico8-data-face
+(defface pico8-data
'((t (:inherit fixed-pitch)))
- "Face for PICO-8 binary data."
- :group 'pico8-faces)
+ "Face for PICO-8 binary data.")
(define-derived-mode pico8-data-mode fundamental-mode
"PICO-8 Data"
"Look up the PICO-8 executable."
(or (car (delete nil (mapcar 'executable-find pico8-executable-paths)))
(error "The PICO-8 executable could not be found.
-Make sure it is installed, and present in `pico8-executable-paths'")))
+Make sure it is installed, and present in ‘pico8-executable-paths’")))
(defun pico8--create-output-buffer ()
"Create and return a buffer for PICO-8 output."
\f
(defun pico8-gff-current-position ()
"Calculate the flag position of the cursor."
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (1- (line-number-at-pos)))
(col (min 255 (current-column))))
(+ (/ col 2) (* row 128)))))
(defun pico8-gff-lighter ()
"Calculate the flag under the cursor."
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (1- (line-number-at-pos)))
(col (current-column)))
(+ (* 128 row) (/ col 2)))))
+(defun pico8-gff-eldoc ()
+ "Show information about the flag under the point."
+ (save-excursion
+ (when (cl-oddp (current-column))
+ (backward-char))
+ (when (looking-at "..")
+ (let* ((hex (substring-no-properties (match-string 0)))
+ (int (string-to-number hex 16)))
+ (format "%08d %3d %02x"
+ (let ((calc-number-radix 2))
+ (string-to-number (math-format-radix int)))
+ int int)))))
+
+(defconst pico8-gff-font-lock-keywords
+ '(("..\n?" 0
+ (prog1 nil
+ ;; TODO: Lots of work to do here…
+ (let ((pdl (or (car (overlays-at (match-beginning 0)))
+ (make-overlay (match-beginning 0) (match-end 0)))))
+ (overlay-put pdl 'after-string " "))))))
+
+
(define-derived-mode pico8-gff-mode pico8-data-mode
'(:eval (format "Flag[%d]" (pico8-gff-lighter)))
- "Major mode for editing flags in PICO-8 cartridges.")
+ "Major mode for editing flags in PICO-8 cartridges."
+ (font-lock-add-keywords nil pico8-gff-font-lock-keywords)
+ (setq-local eldoc-documentation-function #'pico8-gff-eldoc))
(defun pico8-gff-offset-of-flag (flag)
"Calculate the offset of of flag number FLAG."
(unless (<= 0 flag 255)
(error "Valid flag numbers are 0 to 255, inclusive"))
(+ (* 2 flag) (if (> flag 128) 1 0)))
-\f
-(defgroup pico8-pixel-faces nil
- "Font faces to use for PICO-8 pixels.
-
-Rather than customizing each directly, you'll probably just want
-to change `pico8-pixel-face'."
- :tag "PICO-8 Pixel Faces"
- :group 'pico8-faces)
-
-(defface pico8-pixel-face
- '((t (:inherit pico8-data-face :height 100)))
- "Face for PICO-8 sprite 'pixels'."
- :group 'pico8-faces)
-(dotimes (i (length pico8-colors))
- (let ((c (nth i pico8-colors)))
- (eval `(defface ,(intern (format "pico8-pixel-%x" i))
- '((t (:inherit pico8-pixel-face :foreground ,c)))
- ,(format "Face for PICO-8 sprite 'pixel' %x" i)
- :group 'pico8-pixel-faces
- :tag ,(format "Face for PICO-8 sprite 'pixel' %x." i)))))
+\f
+(defface pico8-pixel
+ '((t (:inherit pico8-data :height 100)))
+ "Face for PICO-8 sprite “pixels.”"
+ :group 'pico8)
(defconst pico8-gfx-font-lock-keywords
- `(("0+" . 'pico8-pixel-0)
- ("1+" . 'pico8-pixel-1)
- ("2+" . 'pico8-pixel-2)
- ("3+" . 'pico8-pixel-3)
- ("4+" . 'pico8-pixel-4)
- ("5+" . 'pico8-pixel-5)
- ("6+" . 'pico8-pixel-6)
- ("7+" . 'pico8-pixel-7)
- ("8+" . 'pico8-pixel-8)
- ("9+" . 'pico8-pixel-9)
- ("a+" . 'pico8-pixel-a)
- ("b+" . 'pico8-pixel-b)
- ("c+" . 'pico8-pixel-c)
- ("d+" . 'pico8-pixel-d)
- ("e+" . 'pico8-pixel-e)
- ("f+" . 'pico8-pixel-f)
-
- ;; If the \n isn't in the smaller face the line is taller to
- ;; accommodate the full sized point at the end-of-line.
- ("\n" . 'pico8-pixel-0)))
+ (cons
+ ;; If the \n isn't in the smaller face the line is taller to
+ ;; accommodate the full sized point at the end-of-line.
+ '("\n" . 'pico8-pixel)
+ (-map-indexed
+ (lambda (i c)
+ `(,(format "%x+" i)
+ 0 '(face (:inherit pico8-pixel :foreground ,c))))
+ pico8-colors)))
+
(defun pico8-gfx-current-position ()
"Calculate the sprite and in-sprite position of the cursor."
;; FIXME: Ensure the span we got was actually the gfx one.
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (1- (line-number-at-pos)))
(col (min 127 (current-column))))
(list (+ (* 16 (/ row 8)) (/ col 8))
(% (+ 256 n (nth 0 current)) 256)
(nth 1 current)
(nth 2 current))))
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(goto-char (+ (point-min) offset)))))
(defun pico8-backward-sprite (n)
(define-key pico8-gfx-mode-map "q" 'pico8-backward-sprite)
(define-key pico8-gfx-mode-map "w" 'pico8-forward-sprite)
-(defface pico8-map-tile-face
- '((t (:inherit pico8-data-face :height 100)))
+(defface pico8-map-tile
+ '((t (:inherit pico8-data :height 100)))
"Face for PICO-8 map 'tiles'."
:group 'pico8-faces)
(defconst pico8-map-font-lock-keywords
- '(("[0-9a-f]+" . 'pico8-map-tile-face)
- ("\n" . 'pico8-map-tile-face)))
+ '(("[0-9a-f]+" . 'pico8-map-tile)
+ ("\n" . 'pico8-map-tile)))
(defun pico8-map-lighter ()
"Calculate the map tile under the cursor."
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (- (line-number-at-pos) 1))
(col (current-column)))
;; TODO: Show sprite number and flags value
\f
(defun pico8-sfx-lighter ()
"Calculate the sound effect under the cursor."
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (- (line-number-at-pos) 1)))
(format "%d" row))))
\f
(defun pico8-music-lighter ()
"Calculate the map tile under the cursor."
- (pm-with-narrowed-to-span (pm-get-innermost-span)
+ (pm-with-narrowed-to-span (pm-innermost-span)
(let ((row (- (line-number-at-pos) 1)))
(format "%d" row))))
"Get the sprite number relevant to the point.
When editing a flag, this is the flag number. When editing a
-map, this is the value at the map. When editing Lua code,
-this is the numeric literal in the code."
+map, this is the value at the map. When editing Lua code, this
+is the numeric literal in the code."
(cond
((derived-mode-p 'pico8-gff-mode) (pico8-gff-current-position))
"Go to the sprite number relevant to the text at the point.
When editing a flag, this is the flag number. When editing a
-map, this is the value at the map. When editing Lua code,
-this is the numeric literal in the code."
+map, this is the value at the map. When editing Lua code, this
+is the numeric literal in the code."
(interactive)
(let ((sprite (pico8-sprite-relevant-to-point)))
(if sprite (pico8-goto-sprite sprite)
(flycheck-define-checker pico8-lua
"A PICO-8 Lua syntax checker using the Lua compiler.
-See URL `http://www.lua.org/'."
+See URL ‘http://www.lua.org/'."
:command ("luac" "-p" source)
:standard-input nil
:error-patterns
(flycheck-define-checker pico8-luacheck
"A PICO-8 Lua syntax checker using luacheck.
-See URL `https://github.com/mpeterv/luacheck'."
+See URL ‘https://github.com/mpeterv/luacheck’."
:command ("luacheck"
"--formatter" "plain"
"--codes" ; Show warning codes
(error line-start
(optional (file-name))
":" line ":" column ":"
- ;; `luacheck' before 0.11.0 did not output codes for errors, hence
+ ;; ‘luacheck’ before 0.11.0 did not output codes for errors, hence
;; the ID is optional here
(optional " (" (id "E" (one-or-more digit)) ") ")
(message) line-end))
(defmacro pico8--defchunkmode (name)
"Define a PICO-8 polymode chunk for section NAME."
`(defconst ,(intern (concat "pico8--pm-inner-" name))
- (pm-hbtchunkmode :mode ',(intern (format "pico8-%s-mode" name))
- :head-mode 'host
- :head-reg ,(format "^__%s__\n" name)
- :tail-reg "^__[a-z]\\{3,5\\}__\n\\|^\n\\'")))
-
-(defconst pico8--pm-poly
- (pm-polymode-multi
- :hostmode
- (defconst pico8--pm-host
- (pm-bchunkmode :mode 'pico8-cartridge-mode))
+ (pm-inner-chunkmode
+ :name ,name
+ :mode ',(intern (format "pico8-%s-mode" name))
+ :head-mode 'host
+ :head-matcher ,(format "^__%s__\n" name)
+ :tail-matcher "^__[a-z]\\{3,5\\}__\n\\|^\n\\'")))
+
+
+(defconst pico8--pm-host
+ (pm-host-chunkmode :name "PICO-8" :mode 'pico8-cartridge-mode))
+
+(define-polymode pico8-mode
+ :hostmode 'pico8--pm-host
:innermodes (list
(pico8--defchunkmode "lua")
(pico8--defchunkmode "gfx")
(pico8--defchunkmode "gff")
(pico8--defchunkmode "map")
(pico8--defchunkmode "sfx")
- (pico8--defchunkmode "music"))))
-
-(define-polymode pico8-mode pico8--pm-poly
- :lighter "P8"
- :keymap '(("\C-c\C-r" . pico8-run-cartridge)
+ (pico8--defchunkmode "music"))
+ :lighter "P8"
+ :keymap '(("\C-c\C-r" . pico8-run-cartridge)
("\C-c\C-e" . pico8-load-cartridge)
("\M-gs" . pico8-goto-sprite)
("\M-gS" . pico8-goto-sprite-relevant-to-point)
(provide 'pico8)
;;; pico8.el ends here
+
+;; Local Variables:
+;; sentence-end-double-space: t
+;; End: