Login
7 branches 0 tags
Ben (X13/Arch) Simplified things a little 0643405 9 days ago 1260 Commits
nujel / stdlib_modules / app / termed.nuj
(import (TermApp) :term/TermApp)
(import (TextBuffer) :app/termed/buffer)

(def clipboard "")
(def buffers [])
(def bgcolor 1)

(defclass TextEditor
  "A terminal-based text editor with controls very similar to Emacs"

  (defn new (self term args)
        (def ret { :term term
                   :cur-buffer #nil
                   :screen-width 0
                   :screen-height 0
                   :scroll-interval 8
                   :cx-active #f
                   :cx-key-down-handler {}
                   :key-down-handler {}
                   :focus? #f
                   :prototype* self })

        (:text-mode ret)
        (when (zero? (:length buffers))
          (:new-buffer ret "*scratch*"))
        (doseq (arg args)
               (:load-buffer ret arg))
        ret)

  (defn set-cur-buffer! (self buf)
        (set! self.scroll-interval (max 4 (int (/ self.screen-height 3))))
        (set! buf.scroll-interval self.scroll-interval)
        (set! self.cur-buffer buf))

  (defn new-buffer (self filename)
        (def buf (:new TextBuffer filename))
        (array/push buffers buf)
        (:set-cur-buffer! self buf)
        buf)

  (defn yank (self)
        (:insert-text self.cur-buffer clipboard))

  (defn kill-line (self)
        (set! clipboard (:kill-line self.cur-buffer)))

  (defn accent-color (self)
        (if self.focus?
            #xF4
            #xF8))

  (defn draw-modeline (self buf)
        (def colored-modeline (cat " " (pad-end (:get-modeline buf) (dec self.screen-width))))
        (:draw-text self.term colored-modeline 1 (dec self.screen-height) self.screen-width 1 (:accent-color self)))

  (defn draw-buffer-line (self buf term-x term-y term-w line-y)
        (def line (:get-line buf line-y))
        (when-not line
                  (:draw-text self.term (pad-end "" term-w) term-x term-y term-w 1 #xFF)
                  (return))
        (def gutter-color (if (= line-y buf.pos-y) (:accent-color self) #xFF))
        (def ln (fmt " {:4} " (inc line-y)))
        (:draw-text self.term ln term-x term-y 6 1 gutter-color)
        (def buf-off 0)
        (def buf-max (:length line))
        (def max-w (min (- buf-max buf-off) (- term-w 5)))
        (def mark-start 0)
        (def mark-end 0)
        (when (:use-region? buf)
          (def rs-y (:region-start-y buf))
          (def re-y (:region-end-y buf))
          (def rs-x (:region-start-x buf))
          (def re-x (:region-end-x buf))
          (when (and (>= line-y rs-y)
                     (<= line-y re-y))
            (set! mark-end max-w)
            (when (= line-y rs-y)
              (set! mark-start rs-x))
            (when (= line-y re-y)
              (set! mark-end re-x))))
        (def c 0)
        (def color 0)
        (:draw-char self.term #x20 (+ 6 term-x) term-y #xFF)
        (dotimes (i term-w)
          (if (< i max-w)
              (do (set! c (ref line (+ i buf-off)))
                  (set! color (if (and (>= i mark-start)
                                       (< i mark-end)
                                       (not (zero? c)))
                                  (:accent-color self) #xFF))
                (:draw-char self.term (if (zero? c) #x20 c) (+ 6 term-x i) term-y color))
              (:draw-char self.term #x20 (+ 6 term-x i) term-y color))))

  (defn draw-buffer (self buf x y w h)
        (dotimes (cy h)
          (:draw-buffer-line self buf x (+ cy y) w (+ cy buf.scroll-y)))
        (:draw-modeline self buf))

  (defn draw-screen (self)
        (:draw-buffer self self.cur-buffer 1 1 self.screen-width (- self.screen-height 2))
        (:set-cursor self.term
                     (+ 1 6 self.cur-buffer.pos-x)
                     (- (inc self.cur-buffer.pos-y)
                        self.cur-buffer.scroll-y)))

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

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

  (defn set-parent! (self parent)
        (set! self.term parent))

  (defn scroll-into-view (self)
        (def buf self.cur-buffer)
        (def frame-height (- self.screen-height 3))
        (when (< (- buf.pos-y buf.scroll-y) 0)
          (set! buf.scroll-y (* self.scroll-interval (int (floor (/ buf.pos-y self.scroll-interval))))))
        (when (> (- buf.pos-y buf.scroll-y) frame-height)
          (set! buf.scroll-y (* self.scroll-interval (int (ceil (/ (- buf.pos-y frame-height) self.scroll-interval))))))
        (when (< buf.scroll-y 0)
          (set! buf.scroll-y 0)))

  (defn handle-key-down (self key)
        (def handler (ref (if self.cx-active self.cx-key-down-handler self.key-down-handler) key))
        (set! self.cx-active #f)
        (when handler (handler self.cur-buffer)))

  (defn kill-region (self)
        (set! clipboard (:get-region self.cur-buffer))
        (:delete-region self.cur-buffer))

  (defn kill-ring-save (self)
        (set! clipboard (:get-region self.cur-buffer))
        (:keyboard-quit self.cur-buffer))

  (defn resize (self width height)
        (set! self.screen-width width)
        (set! self.screen-height height)
        (:set-cur-buffer! self self.cur-buffer))

  (defn insert-char (self event)
        (def handler (ref self.key-down-handler (:keyword event.char)))
        (set! self.cx-active #f)
        (if handler
            (handler self.cur-buffer)
            (:insert-char self.cur-buffer event.code)))

  (defn handle-event (self event)
        (case event.T
              (:blur (set! self.focus? #f))
              (:reparent (set! self.term event.parent))
              (:focus (set! self.focus? #t))
              (:draw (:scroll-into-view self) (:draw-screen self))
              (:resize (:resize self event.width event.height))
              (:key-down (:handle-key-down self event.key))
              (:input (if self.cx-active
                          (:handle-key-down self (:keyword event.char))
                          (:insert-char self event)))))

  (defn load-buffer (self filename)
        (def buf (:new-buffer self filename))
        (def text (slurp filename))
        (def raw-lines (if text (split text "\n") #nil))
        (def lines (:alloc Array (max 1 (:length raw-lines))))
        (set! buf :lines lines)
        (def i 0)
        (doseq (line raw-lines buf)
               (def cur-line (buffer/allocate (bit-shift-left (inc (bit-shift-right (:length line) 4)) 4)))
               (buffer/copy cur-line line 0 (:length line))
               (set! lines i cur-line)
               (inc! i)))

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

  (defn windmove-right (self)
        (:windmove-right self.term))

  (defn windmove-up (self)
        (:windmove-up self.term))

  (defn windmove-down (self)
        (:windmove-down self.term))

  (defn set-parent! (self parent)
        (set! self.term parent))

  (defn split-window-right (self)
        (def new (:new TextEditor))
        (:set-cur-buffer! new self.cur-buffer)
        (:split-window-right self.term new))

  (defn split-window-below (self)
        (def new (:new TextEditor))
        (:set-cur-buffer! new self.cur-buffer)
        (:split-window-below self.term new))

  (defn run (self)
        (:run self.term self))

  (defn fundamental-mode (self)
        (def key-down-handler self.key-down-handler)
        (def cx-key-down-handler self.cx-key-down-handler)

        (set! key-down-handler :C-x (fn () (set! self.cx-active #t)))
        (set! key-down-handler :C-spc :set-mark-command)
        (set! key-down-handler :C-a :start-of-line)
        (set! key-down-handler :C-b :backward-char)
        (set! key-down-handler :C-e :end-of-line)
        (set! key-down-handler :C-f :forward-char)
        (set! key-down-handler :C-g :keyboard-quit)
        (set! key-down-handler :C-p :previous-line)
        (set! key-down-handler :C-n :next-line)
        (set! key-down-handler :C-up (fn () (:windmove-up self)))
        (set! key-down-handler :C-down (fn () (:windmove-down self)))
        (set! key-down-handler :C-right (fn () (:windmove-right self)))
        (set! key-down-handler :C-left (fn () (:windmove-left self)))

        (set! key-down-handler :C-w (fn () (:kill-region self)))
        (set! key-down-handler :M-w (fn () (:kill-ring-save self)))
        (set! key-down-handler :M-< :beginning-of-buffer)
        (set! key-down-handler :M-> :end-of-buffer)

        (set! key-down-handler :left :backward-char)
        (set! key-down-handler :right :forward-char)
        (set! key-down-handler :up :previous-line)
        (set! key-down-handler :down :next-line)
        (set! key-down-handler :home :start-of-line)
        (set! key-down-handler :end :end-of-line)
        (set! key-down-handler :page-up :scroll-down-command)
        (set! key-down-handler :page-down :scroll-up-command)

        (set! cx-key-down-handler :h :mark-whole-buffer)
        (set! cx-key-down-handler :2 (fn () (:split-window-below self)))
        (set! cx-key-down-handler :3 (fn () (:split-window-right self)))
        (set! cx-key-down-handler :0 (fn () (:stop self)))
        (set! cx-key-down-handler :C-c (fn () (:quit self))))

  (defn text-mode (self)
        (:fundamental-mode self)
        (def key-down-handler self.key-down-handler)

        (set! key-down-handler :C-k (fn () (:kill-line self)))
        (set! key-down-handler :backspace :backward-delete-char)
        (set! key-down-handler :delete :delete-forward-char)
        (set! key-down-handler :C-y (fn () (:yank self)))
        (set! key-down-handler :insert (fn () (:yank self)))
        (set! key-down-handler :ret :insert-newline)))

(defn main (args)
      :export
      (:run (:new TextEditor (:new TermApp) args)))