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))
           (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))))))

(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*)

      (let* ((*input-handler* (make-instance 'input-handler))
             (*cursor* (make-instance 'cursor :action (c? (action *input-handler*))))
             (screen-dimensions (multiple-value-list (window-dimensions *standard-window*))))

        (defobserver action ((self input-handler))
                    (case new-value
                      ((:paint) (paint))))


        (defobserver y ((self cursor))
                     (with-integrity (:change)
                                     (let ((adjusted-n-v (mod new-value
                                                              (cadr screen-dimensions))))
                                       (if (/= new-value adjusted-n-v)
                                         (setf (^cursor-y) (- adjusted-n-v new-value))
                                         (move-cursor *standard-window*
                                                      (^cursor-x) adjusted-n-v)))))

        (defobserver x ((self cursor))
                     (with-integrity (:change)
                                     (let ((adjusted-n-v (mod new-value
                                                              (car screen-dimensions))))
                                       (if (/= new-value adjusted-n-v)
                                         (setf (^cursor-x) (- adjusted-n-v new-value))
                                         (move-cursor *standard-window*
                                                      adjusted-n-v (^cursor-y))))))

        (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)))))