Login
7 branches 0 tags
Ben (X13/Arch) Simplified things a little 0643405 9 days ago 1260 Commits
nujel / stdlib_modules / term / TermApp.nuj
;;; Nujel - Copyright (C) 2020-2021 - Benjamin Vincent Schulenburg
;;; This project uses the MIT license, a copy should be included under /LICENSE
;;;
(import (VT100) :term/VT100)
(import (TermAppVerticalSplit TermAppHorizontalSplit) :term/TermAppSplit)

(defclass TermApp
  "A high-level I/O interface for a terminal"
  :export

  (defn new (self in out)
        (def term (:new VT100 in out))
        (:start term)
        (def ret { :term term
                   :width 0
                   :height 0
                   :frontbuffer #nil
                   :backbuffer #nil
                   :input-buffer (buffer/allocate 16)
                   :input-buffer-pos 0
                   :event-queue #nil
                   :child #nil
                   :cursor-x -1
                   :cursor-y -1
                   :prototype* self })
        (:resize ret))

  (defn handle-event (self event)
        (:handle-event self.child event))

  (defn split-window-right (self right-child)
        (def new (:new TermAppVerticalSplit self self.child right-child))
        (set! self.child new)
        (:handle-event new {:T :resize :width self.width :height self.height}))

  (defn split-window-below (self below-child)
        (def new (:new TermAppHorizontalSplit self self.child below-child))
        (set! self.child new)
        (:handle-event new {:T :resize :width self.width :height self.height}))

  (defn run (self child)
        (when self.child
          (set! self.child child)
          (let ((s (:get-size self)))
                  (:handle-event child {:T :resize :width s.width :height s.height})
                  (:handle-event child {:T :focus})
                  (:handle-event child {:T :draw}))
          (return))
        (try (fn (e)
                 (spit "err.log" (fmt "{e:?}\n"))
               (:stop self)
               (exit 0))
             (:clear-screen self)
             (def draw {:T :draw})
             (let ((s (:get-size self)))
                  (:handle-event child {:T :resize :width s.width :height s.height})
                  (:handle-event child {:T :focus})
                  (:handle-event child draw))
             (set! self.child child)
             (:flip self)
             (while #t
               (:poll-input self) ;; Poll once so we do end up blocking on IO
               (while (not (:input-would-block? self))
                 (:poll-input self))
               (doseq (event self.event-queue)
                      (:handle-event self.child event))
               (:handle-event self.child draw)
               (:flip self)
               (set! self.event-queue #nil))))

  (defn stop (self)
        (:stop self.term)
        (exit 0))

  (defn quit (self)
        (:stop self.term)
        (exit 0))

  (defn windmove-up (self) #f)
  (defn windmove-down (self) #f)
  (defn windmove-left (self) #f)
  (defn windmove-right (self) #f)

  (defn resize (self)
        (def term self.term)
        (def size (:get-size term))
        (def buf-size (* 2 size.width size.height))
        (set! self.width size.width)
        (set! self.height size.height)
        (set! self.frontbuffer (buffer/allocate buf-size))
        (set! self.backbuffer (buffer/allocate buf-size)))

  (defn flip (self)
        (def backbuffer self.backbuffer)
        (def frontbuffer self.frontbuffer)
        (def term self.term)
        (:hide-cursor term)
        (def last-color -1)
        (dotimes (y self.height)
                 (dotimes (x self.width)
                          (def off (* 2 (+ x (* y self.width))))
                          (def bc  (+ (ref backbuffer off)
                                      (bit-shift-left (ref backbuffer (inc off)) 8)))
                          (def fc  (+ (ref frontbuffer off)
                                      (bit-shift-left (ref frontbuffer (inc off)) 8)))
                          (when (not= fc bc)
                            (:move-cursor term x y)
                            (when (not= (bit-shift-right bc 8) last-color)
                              (:set-color-code term (bit-shift-right bc 8))
                              (set! last-color (bit-shift-right bc 8)))
                            (:put-char term (bit-and #xFF bc))
                            (set! frontbuffer off (bit-and bc #xFF))
                            (set! frontbuffer (inc off) (bit-shift-right bc 8)))))
        (when (and (> self.cursor-x 0)
                   (> self.cursor-y 0)
                   (< self.cursor-x self.width)
                   (< self.cursor-y self.height))
          (:move-cursor term self.cursor-x self.cursor-y)
          (:show-cursor term))
        (:flip self.term)
        self)

  (defn draw-char (self char x y color)
        (when (or (>= x self.width)
                  (>= y self.height)
                  (< x 0)
                  (< y 0))
          (return))
        (def off (* 2 (+ x (* y self.width))))
        (def color color)
        (set! self.backbuffer off char)
        (set! self.backbuffer (inc off) color))

  (defn set-cursor (self x y)
       (set! self.cursor-x x)
       (set! self.cursor-y y))

  (defn clear-screen (self)
        (def bb self.backbuffer)
        (def fb self.frontbuffer)
        (dotimes (i (:length bb) self)
          (set! fb i #xFF)
          (set! bb i 0)))

  (defn draw-text (self text x y w h color)
        (set! x (max 0 x))
        (set! y (max 0 y))
        (set! w (min (min w (- self.width x)) (:length text)))
        (set! h (min (- self.height y) (- h 1)))
        (def off (* 2 (+ x (* y self.width))))
        (def bb self.backbuffer)
        (dotimes (i w self)
                 (set! bb off (ref text i))
                 (set! bb (inc off) color)
                 (set! off (+ off 2))))

  (defn get-size (self)
        { :width self.width
          :height self.height })

  (defn pop-input-buffer! (self bytes)
        (dotimes (i (:length self.input-buffer))
          (def src-pos (+ i self.input-buffer-pos))
          (def src-key (if (< src-pos (:length self.input-buffer))
                           (ref self.input-buffer (+ i self.input-buffer-pos))
                           0))
          (set! self.input-buffer i src-key))
        (set! self.input-buffer-pos 0)
        (return self))

  (defn add-input-event (self event)
        (set! self.event-queue (cons event self.event-queue)))

  (defn parse-input-buffer (self)
        (when (zero? self.input-buffer-pos) (return))

        (def c (ref self.input-buffer 0))
        (when (= c 194) ;; Meta digit Input
          (def d (ref self.input-buffer 1))
          (when (= d 0)
            (return))
          (when (and (>= d 160) (<= d 191))
            (def key (:keyword (fmt "M-{}" (from-char-code (+ #x20 (- d 160))))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (exception :invalid-input (fmt "Unknown meta input: {d:X}") d))
        (when (= c 195) ;; Meta Char Input
          (def d (ref self.input-buffer 1))
          (when (= d 0)
            (return))
          (when (and (>= d 129) (<= d 186))
            (def key (:keyword (fmt "M-{}" (from-char-code (+ #x20 (- d 96))))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (exception :invalid-input (fmt "Unknown meta input: {d:X}") d))
        (when (= c #x1B) ; Escaped Input
          (def d (ref self.input-buffer 1))
          (when (= d #x1B) ; Double Escape
            (:add-input-event self { :T :key-down :key :escape })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (when (= d 79) ; F Input
            (def e (ref self.input-buffer 2))
            (when (and (>= e 80) (< e 92))
              (def key (:keyword (fmt "F{}" (- e 79))))
              (:add-input-event self { :T :key-down :key key })
              (:pop-input-buffer! self 3)
              (return (:parse-input-buffer self))))
          (when (= d #\[) ; Bracketed Input
            (def e (ref self.input-buffer 2))
            (case e
                  (0 (return))
                  #;(0 (:add-input-event self { :T :key-down :key (:keyword "M-[") })
                     (:pop-input-buffer! self 2)
                     (return (:parse-input-buffer self)))
                  ((#x31 #x32 #x33 #x35 #x36)
                   (def f (ref self.input-buffer 3))
                   (when (= f 0) (return))
                   (when (= f #x3B)
                     (when (zero? (ref self.input-buffer 4))
                       (return))
                     (def prefix (case (ref self.input-buffer 4)
                                       (53 "C-")
                                       (50 "S-")
                                       (otherwise (exception :invalid-input (fmt "Unknown C-arrow 4 suffix: {e:X}") (ref self.input-buffer 4)))))
                     (def dir (ref self.input-buffer 5))
                     (when (zero? dir)
                       (return))
                     (case dir
                           (#\A (:add-input-event self { :T :key-down :key (:keyword (cat prefix "up"))}))
                           (#\B (:add-input-event self { :T :key-down :key (:keyword (cat prefix "down"))}))
                           (#\C (:add-input-event self { :T :key-down :key (:keyword (cat prefix "right"))}))
                           (#\D (:add-input-event self { :T :key-down :key (:keyword (cat prefix "left"))}))
                           (#\F (:add-input-event self { :T :key-down :key (:keyword (cat prefix "end"))}))
                           (#\H (:add-input-event self { :T :key-down :key (:keyword (cat prefix "home"))}))
                           (#x7E (case e
                                       (#x32 (:add-input-event self { :T :key-down :key :C-insert }))
                                       (#x33 (:add-input-event self { :T :key-down :key :C-delete }))
                                       (#x35 (:add-input-event self { :T :key-down :key :C-page-up }))
                                       (#x36 (:add-input-event self { :T :key-down :key :C-page-down }))
                                       (otherwise (exception :invalid-input (fmt "Unknown C-special: {e:X}")))))
                           (otherwise (exception :invalid-input (fmt "Unknown C-arrow dir: {dir:X}"))))
                     (:pop-input-buffer! self 6)
                     (return (:parse-input-buffer self)))
                   (when (= f #x37)
                     (:add-input-event self { :T :key-down :key :print })
                     (:pop-input-buffer! self 4)
                     (return (:parse-input-buffer self)))
                   (when (= f 126)
                     (case e
                           (#x32 (:add-input-event self { :T :key-down :key :insert }))
                           (#x33 (:add-input-event self { :T :key-down :key :delete }))
                           (#x35 (:add-input-event self { :T :key-down :key :page-up }))
                           (#x36 (:add-input-event self { :T :key-down :key :page-down }))
                           (otherwise (exception :invalid-input (fmt "Unknown bracketed arrow input: {e:X}") e)))
                     (:pop-input-buffer! self 4)
                     (return (:parse-input-buffer self)))
                   (exception :invalid-input (fmt "Unknown bracketed input suffix: {f:X}") f))
                  (#\A (:add-input-event self { :T :key-down :key :up }))
                  (#\B (:add-input-event self { :T :key-down :key :down }))
                  (#\C (:add-input-event self { :T :key-down :key :right }))
                  (#\D (:add-input-event self { :T :key-down :key :left }))
                  (#\F (:add-input-event self { :T :key-down :key :end }))
                  (#\H (:add-input-event self { :T :key-down :key :home }))
                  (otherwise (exception :invalid-input (fmt "Unknown bracketed input: {e:X}") e)))
            (:pop-input-buffer! self 3)
            (return (:parse-input-buffer self)))
          (when (= d 127)
            (:add-input-event self { :T :key-down :key :M-backspace })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (when (= d #\Tab)
            (:add-input-event self { :T :key-down :key :M-tab })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (when (and (>= d #\0) (<= d #\9))
            (def key (:keyword (fmt "M-{}" (from-char-code d))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (when (and (>= d #x20) (< d #x79))
            (def key (:keyword (fmt "M-{}" (from-char-code d))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self)))
          (when (and (>= d #\a) (<= d #\z))
            (def key (:keyword (fmt "M-{}" (from-char-code d))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 2)
            (return (:parse-input-buffer self))))
        (when (< c 27)
          (when (= c 0)
            (:add-input-event self { :T :key-down :key :C-spc })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (when (= c 8)
            (:add-input-event self { :T :key-down :key :backspace })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (when (< c 9)
            (def key (:keyword (fmt "C-{}" (from-char-code (+ #\a (dec c))))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (when (= c 9)
            (:add-input-event self { :T :key-down :key :tab })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (when (= c 13)
            (:add-input-event self { :T :key-down :key :ret })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (def key (:keyword (fmt "C-{}" (from-char-code (+ #\j (- c 10))))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
        (when (and (> c 27) (< c 32))
          (when (= c 31)
            (:add-input-event self { :T :key-down :key :C-/ })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self)))
          (when (> c 27)
            (def key (:keyword (fmt "C-{}" (from-char-code (+ #\[ (- c 27))))))
            (:add-input-event self { :T :key-down :key key })
            (:pop-input-buffer! self 1)
            (return (:parse-input-buffer self))))
        (when (= c 127)
          (:add-input-event self { :T :key-down :key :backspace })
          (:pop-input-buffer! self 1)
          (return (:parse-input-buffer self)))
        (when (and (>= c #x20) (<  c #x80))
          (:add-input-event self { :T :input
                                   :code c
                                   :char (buffer->string self.input-buffer 1 0)
                                   })
          (:pop-input-buffer! self 1)
          (return (:parse-input-buffer self))))

  (defn parse-input-key (self key)
        (when key
          (:add-input-event self { :T :raw-input :code key })
          (set! self.input-buffer self.input-buffer-pos key)
          (set! self.input-buffer-pos (inc self.input-buffer-pos)))
        (:parse-input-buffer self))

  (defn get-events (self)
        (when-not self.event-queue (return #nil))
        (def ret (car self.event-queue))
        (set! self.event-queue (cdr self.event-queue))
        (return ret))

  (defn input-would-block? (self)
        (:input-would-block? self.term))

  (defn input-left (self)
        (:input-left self.term))

  (defn poll-input (self)
        (def key (:poll-input self.term))
        (:parse-input-key self key)
        key)

  (defn poll-events (self)
        (:poll-input self)
        (:get-events self)))