git.fiddlerwoaroof.com
Raw Blame History
(load #p"~/quicklisp/setup.lisp")
(declaim (optimize (speed 0) (safety 3) (debug 3)))

(ql:quickload :cl-charms)
(ql:quickload :cells)
(ql:quickload :alexandria)
(ql:quickload :anaphora)

(defpackage :lisp-edit
  (:use :cl :cl-charms :cells :alexandria :anaphora))
(in-package :lisp-edit)

(defun apply-cursor-action (action)
  (case action
    ((:up)    (cons  0 -1))
    ((:down)  (cons  0  1))
    ((:left)  (cons -1  0))
    ((:right) (cons  1  0))))

(defun dispatch-char (c)
  (case c
    ((nil) nil)
    ((#\j) :down )
    ((#\h) :left )
    ((#\k) :up   )
    ((#\l) :right)
    ((#\f) :paint)
    ((#\q #\Q) :quit)))

(defmodel input-handler ()
          ((input-key :cell :ephemeral :accessor input-key :initform (c-in nil))
           (action :cell :ephemeral :accessor action :initform (c? (dispatch-char (^input-key))))))

(defmodel cursor ()
          ((action :cell :ephemeral :initarg :action :accessor action :initform (c-in nil))
           (current-char :initarg :current-char :accessor current-char :initform (c-in nil))
           (delta :accessor cursor-delta :initform
                  (c? (apply-cursor-action (^action))))
           (x :accessor cursor-x :initarg :x :initform (c... (0) (car (^cursor-delta))))
           (y :accessor cursor-y :initarg :y :initform (c... (0) (cdr (^cursor-delta))))))

(defun map-actions (current-char action)
  (case action
    ((:paint) (case current-char
                ((#\#) #\Space)
                ((#\Space) #\*)
                ((#\*) #\#)))))

(defmodel painter ()
          ((action :cell :ephemeral :initarg :action :accessor action :initform (c-in nil))
           (current-char :initarg :current-char :accessor current-char :initform (c-in nil))
           (output :cell :ephemeral :accessor output :initform (c? (map-actions (^current-char)
                                                                                (^action))))))

(defvar *input-handler*)
(defvar *painter*)
(defvar *cursor*)

(defun paint ()
  (with-restored-cursor *standard-window*
    (write-char-at-cursor *standard-window*
                          (if (char/= #\Space (char-at-cursor *standard-window*))
                            #\Space
                            #\*))))

(defun constrain-pos (elem pos delta window-dimensions)
  (let* ((accessor (case elem
                     ((:x) #'car)
                     ((:y) #'cdr)))
         (window-elem (funcall accessor window-dimensions))
         (delta-elem (funcall accessor delta))
         (pos-elem (funcall accessor pos))
         (naive-delta (mod (+ pos-elem delta-elem)
                           window-elem))
         (real-delta (- naive-delta pos-elem)))
    real-delta))


(defun main ()
  (cells-reset)
  (handler-case
    (with-curses ()
      (disable-echoing)
      (enable-raw-input :interpret-control-characters t)
      (enable-non-blocking-mode *standard-window*)

      (setf *input-handler* (make-instance 'input-handler) 
            *cursor* (make-instance 'cursor :action (c? (action *input-handler*)))
            *painter* (make-instance 'painter
                                     :action (c? (action *input-handler*))
                                     :current-char (c? (current-char *cursor*))))

      (let* ((screen-dimensions (multiple-value-bind (x y) (window-dimensions *standard-window*)
                                  (cons x y))))
        (labels
          ((set-current-char (cursor)
             (with-restored-cursor *standard-window*
               (setf (current-char cursor) (char-at-cursor *standard-window*))))
           (mvc (x y)
             (move-cursor *standard-window* x y)
             (set-current-char *cursor*))
           (constrain-coordinate (side coord)
             (mod coord (funcall
                          (ecase side
                            ((:x) #'car)
                            ((:y) #'cdr))
                          screen-dimensions))))

          (defobserver output ((self painter))
                       (with-restored-cursor *standard-window*
                         (write-char-at-cursor *standard-window* new-value))
                       (with-integrity (:change)
                         (set-current-char *cursor*)))

          (defobserver y ((self cursor))
                       (with-integrity (:change)
                         (let ((adjusted-n-v (constrain-coordinate :y new-value)))
                           (if (/= new-value adjusted-n-v)
                             (setf (^cursor-y) (- adjusted-n-v new-value))
                             (mvc (^cursor-x) adjusted-n-v)))))

          (defobserver x ((self cursor))
                       (with-integrity (:change)
                         (let ((adjusted-n-v (constrain-coordinate :x new-value)))
                           (if (/= new-value adjusted-n-v)
                             (setf (^cursor-x) (- adjusted-n-v new-value))
                             (mvc adjusted-n-v (^cursor-y))))))

          (mvc 0 0)
          (loop :named driver-loop
                :for c := (get-char *standard-window* :ignore-error t)
                :do (progn
                      (refresh-window *standard-window*)
                      (setf (input-key *input-handler*) c))))))

    (sb-sys:interactive-interrupt (c) (declare (ignore c)))))