Login
7 branches 0 tags
Ben (X13/Arch) Nicer termed selection highlight c6a2bfe 2 years ago 1195 Commits
nujel / stdlib_modules / app / termed / buffer.nuj
(defclass TextBuffer
  "A buffer for text editors"
  :export

  (defn new (self filename)
        (def lines [(buffer/allocate 16)])
        { :lines lines
          :filename filename
          :changed #f
          :pos-x 0
          :pos-y 0
          :scroll-x 0
          :scroll-y 0
          :mark #nil
          :scroll-interval 20
          :prototype* self })

  (defn get-line (self line-y)
        (def lines (ref self :lines))
        (if (or (< line-y 0)
                (>= line-y (:length lines)))
            #nil
            (ref lines line-y)))

  (defn line-length (self pos-y)
        (def line (:get-line self pos-y))
        (when-not line (return 0))
        (dotimes (i (:length line) (:length line))
                 (when (zero? (ref line i))
                   (return i))))

  (defn line-indent-depth (self pos-y)
        (def line (:get-line self pos-y))
        (when-not line (return 0))
        (dotimes (i (:length line) (:length line))
                 (when (or (zero? (ref line i))
                           (> (ref line i) #x20))
                   (return i))))

  (defn beginning-of-line (self)
        (set! self :pos-x 0))

  (defn start-of-line (self)
        (def depth (:line-indent-depth self (ref self :pos-y)))
        (if (> (ref self :pos-x) depth)
            (set! self :pos-x depth)
            (:beginning-of-line self)))

  (defn end-of-line (self)
        (set! self :pos-x (:line-length self (ref self :pos-y))))

  (defn beginning-of-buffer (self)
        (set! self :pos-y 0)
        (set! self :pos-x 0))

  (defn end-of-buffer (self)
        (set! self :pos-y (- (:length (ref self :lines)) 1))
        (:end-of-line self))

  (defn backward-char (self)
        (def pos-x (ref self :pos-x))
        (when (< pos-x 1)
          (def pos-y (ref self :pos-y))
          (when (< pos-y 1)
            (return))
          (set! self :pos-y (dec pos-y))
          (set! self :pos-x (:line-length self (dec pos-y)))
          (return))
        (set! self :pos-x (dec pos-x)))

  (defn previous-line (self)
        (def pos-x (ref self :pos-x))
        (def pos-y (ref self :pos-y))
        (when (< pos-y 1)
          (return))
        (set! self :pos-y (dec pos-y))
        (set! self :pos-x (min pos-x (:line-length self (dec pos-y)))))

  (defn next-line (self)
        (def pos-x (ref self :pos-x))
        (def pos-y (ref self :pos-y))
        (def lines (ref self :lines))
        (def ll (- (:length lines) 1))
        (when (>= pos-y ll)
          (return))
        (set! self :pos-y (inc pos-y))
        (set! self :pos-x (min pos-x (:line-length self (inc pos-y)))))

  (defn forward-char (self)
        (def pos-x (ref self :pos-x))
        (def pos-y (ref self :pos-y))
        (def ll (:line-length self pos-y))
        (when (>= pos-x ll)
          (when (>= (inc pos-y) (:length (ref self :lines)))
            (return))
          (set! self :pos-x 0)
          (set! self :pos-y (inc pos-y))
          (return))
        (set! self :pos-x (inc pos-x)))

  (defn delete-forward-char (self)
        (when (:delete-region self) (return))
        (def line (:get-line self (ref self :pos-y)))
        (when-not line (return))
        (def pos-x (ref self :pos-x))
        (def bl (- (:length line) 2))
        (def ll (:line-length self (ref self :pos-y)))
        (when (< bl 0)
          (return))
        (when (>= pos-x ll)
          (when (>= (ref self :pos-y) (- (:length (ref self :lines)) 1))
            (return))
          (set! self :pos-x 0)
          (set! self :pos-y (inc (ref self :pos-y)))
          (def clipboard (:kill-line self))
          (:remove-line self)
          (set! self :pos-y (- (ref self :pos-y) 1))
          (set! self :pos-x pos-x)
          (:insert-text self clipboard)
          (set! self :pos-x pos-x)
          (return))
        (while (<= pos-x bl)
          (set! line pos-x (ref line (inc pos-x)))
          (inc! pos-x))
        (set! line (- (:length line) 1) 0))

  (defn remove-line (self y)
        (when (:delete-region self) (return))
        (set! y (or y (ref self :pos-y)))
        (def lines (ref self :lines))
        (def ll (- (:length lines) 1))
        (def pos-y (ref self :pos-y))
        (when (zero? pos-y) (return))
        (while (< pos-y ll)
          (set! lines pos-y (ref lines (inc pos-y)))
          (inc! pos-y))
        (:length! lines (max 0 ll)))

  (defn backward-delete-line (self)
        (def clipboard (:kill-line self))
        (:remove-line self)
        (set! self :pos-y (- (ref self :pos-y) 1))
        (set! self :pos-x (:line-length self (ref self :pos-y)))
        (def pos-x (ref self :pos-x))
        (:insert-text self clipboard)
        (set! self :pos-x pos-x))

  (defn backward-delete-char (self)
        (when (:delete-region self) (return))
        (def line (:get-line self (ref self :pos-y)))
        (when-not line (return))
        (def pos-x (ref self :pos-x))
        (when (< pos-x 1)
          (return (:backward-delete-line self)))
        (def bl (- (:length line) 1))
        (while (<= pos-x bl)
          (set! line (- pos-x 1) (ref line pos-x))
          (inc! pos-x))
        (set! line bl 0)
        (set! self :pos-x (- (ref self :pos-x) 1)))

  (defn keyboard-quit (self)
        (set! self :mark #nil))

  (defn use-region? (self)
        (pair? (ref self :mark)))

  (defn region-start-y (self)
        (min (ref self :pos-y)
             (cdr (ref self :mark))))

  (defn region-end-y (self)
        (max (ref self :pos-y)
             (cdr (ref self :mark))))

  (defn region-start-x (self)
        (if (= (ref self :pos-y) (cdr (ref self :mark)))
            (min (ref self :pos-x)
                 (car (ref self :mark)))
            (if (< (ref self :pos-y) (cdr (ref self :mark)))
                (ref self :pos-x)
                (car (ref self :mark)))))

  (defn region-end-x (self)
        (if (= (ref self :pos-y) (cdr (ref self :mark)))
            (max (ref self :pos-x)
                 (car (ref self :mark)))
            (if (< (ref self :pos-y) (cdr (ref self :mark)))
                (car (ref self :mark))
                (ref self :pos-x))))

  (defn set-mark-command (self)
        (set! self :mark (cons (ref self :pos-x) (ref self :pos-y))))

  (defn mark-whole-buffer (self)
        (:end-of-buffer self)
        (:set-mark-command self)
        (:beginning-of-buffer self))

  (defn delete-region (self)
        (when (nil? (ref self :mark))
          (return #f))
        (def rs-y (:region-start-y self))
        (def re-y (:region-end-y self))
        (def rs-x (:region-start-x self))
        (def re-x (:region-end-x self))
        (set! self :mark #nil)
        (def times (if (= rs-y re-y)
                       (- re-x rs-x)
                       (+ re-x (- (inc (:line-length self rs-y)) rs-x))))
        (def line-y (inc rs-y))
        (while (< line-y re-y)
          (set! self :pos-y (inc rs-y))
          (:remove-line self)
          (inc! line-y))
        (set! self :pos-x rs-x)
        (set! self :pos-y rs-y)
        (dotimes (i times)
                 (:delete-forward-char self))
        #t)

  (defn get-region (self)
        (when (nil? (ref self :mark))
          (return ""))
        (def rs-y (:region-start-y self))
        (def re-y (:region-end-y self))
        (def rs-x (:region-start-x self))
        (def re-x (:region-end-x self))

        (def buf #nil)
        (def lines (ref self :lines))
        (def line-y rs-y)
        (while (<= line-y re-y)
          (def mark-start 0)
          (def mark-end 0)
          (set! mark-end (:line-length self line-y))
          (when (= line-y rs-y)
            (set! mark-start rs-x))
          (when (= line-y re-y)
            (set! mark-end re-x))
          (set! buf (cons (buffer->string (ref lines line-y) mark-end mark-start) buf))
          (inc! line-y))
        (join (nreverse buf) "\n"))

  (defn insert-char (self c)
        (:delete-region self)
        (when (zero? c) (return))
        (when (= c #\LineFeed)
          (return (:insert-newline self)))
        (def line (:get-line self (ref self :pos-y)))
        (when-not line (return))
        (def pos-x (ref self :pos-x))
        (def i (- (:length line) 1))
        (when-not (zero? (ref line i))
                  (:length! line (+ (:length line) 16))
                  (set! i (- (:length line) 1)))
        (while (> i pos-x)
          (set! line i (ref line (dec i)))
          (dec! i))
        (set! line pos-x c)
        (set! self :pos-x (inc pos-x)))

  (defn insert-text (self text)
        (dotimes (i (:length text))
                 (:insert-char self (ref text i))))

  (defn kill-line (self)
        (set! self :mark #nil)
        (def lines (ref self :lines))
        (def pos-x (ref self :pos-x))
        (def cur-line (ref lines (ref self :pos-y)))
        (def ll (:line-length self (ref self :pos-y)))
        (def ret (buffer->string (:cut cur-line pos-x ll)))
        (while (< pos-x ll)
          (set! cur-line pos-x 0)
          (inc! pos-x))
        (return ret))

  (defn insert-newline (self)
        (:delete-region self)
        (def lines (ref self :lines))
        (:length! lines (inc (:length lines)))

        (def clipboard (:kill-line self))
        (set! self :pos-y (inc (ref self :pos-y)))
        (def ll (ref self :pos-y))
        (def pos-y (- (:length lines) 1))
        (while (> pos-y ll)
          (set! lines pos-y (ref lines (- pos-y 1)))
          (set! pos-y (- pos-y 1)))
        (set! lines (ref self :pos-y) (buffer/allocate 16))
        (set! self :pos-x 0)
        (:insert-text self clipboard)
        (set! self :pos-x 0))

  (defn goto-line (self line)
        (set! self :pos-y (max 0 (min (- (:length (ref self :lines)) 1) line)))
        (set! self :pos-x (max 0 (min (- (:line-length self (ref self :pos-y)) 1) (ref self :pos-x)))))

  (defn scroll-up-command (self)
        (:goto-line self (+ (ref self :pos-y) (ref self :scroll-interval))))

  (defn scroll-down-command (self)
        (:goto-line self (- (ref self :pos-y) (ref self :scroll-interval))))

  (defn get-buf-name (self buf)
        (def filename (ref self :filename))
        (def saved (if (ref self :changed) "*" ""))
        (fmt "{filename}{saved}")))