; .EnTete "Le-Lisp (c) version 15.2" " " "L'e'diteur de ligne"
; .EnPied " " "%" " "
; .Chapitre 16 "L'e'diteur de ligne"
;
; .Centre "*****************************************************************"
; .Centre " Ce fichier est en lecture seule hors du projet ALE de l'INRIA.  "
; .Centre " Il est maintenu par ILOG SA, 2 Avenue Gallie'ni, 94250 Gentilly "
; .Centre " (c) Le-Lisp est une marque de'pose'e de l'INRIA                 "
; .Centre "*****************************************************************"

; .Centre "$Header: edlin.ll,v 4.1 88/01/13 12:19:28 kuczynsk Rel $"

(unless (>= (version) 15.21)
        (error 'load 'erricf 'edlin))

(defvar #:sys-package:colon 'edlin)

(add-feature 'edlin)


; .Section "Gestion d'un historique des commandes"

(setq #:sys-package:colon '#:edlin:history)

(defvar :last-string)
(defvar :maxlen 100)
; l'history commence toujours par une chaine vide
; comme ca, il n'y a plus de flag :at-end
(defvar :history '("" "; In principium erat Le←Lisp..."))
(defvar :position :history)

(de  :show ()
     (terpri 2)
     (:show-aux (cdr :history)))

(de  :show-aux (history)
     ; on ne conse pas : on alloue dynamiquement de la pile
     (if (null history) 0
         (let ((no-line (:show-aux (cdr history))))
              (print (incr no-line) "        " (car history))
              no-line)))

(de  :previous ()
     (if (cdr :position)
         (setq :position (cdr :position))
         (tybeep))
     (car :position))

(de  :next ()
    (if (:endp) (tybeep)
        (let ((history :history))
             (until (eq (cdr history) :position)
                    (nextl history))
             (setq :position history)))
    (car :position))

(de  :add (str)
     (let ((history :history))
          (repeat (sub :maxlen 2) (nextl history))
          (setq :history
                (rplac (if (null (cdr history))
                           ; un nouveau cons
                           (ncons ())
                           ; on reprend le dernier cons
                           (prog1 (cdr history) (rplacd history nil))) 
                       ; on remet la tete d'history (chaine vide)
                       (car :history)
                       ; et la chaine a conserver
                       (rplaca :history str)))))

(de  :find (s)
     (let ((position :position))
          (tag found
               (while (cdr :position)
                      (:previous)
                      (when (index s (car :position))
                            (exit found (car :position))))
               (tybeep)
               (setq :position position)
               () )))

(de  :last ()
     (car (setq :position :history)))

(de  :first ()
     (car (setq :position (last :history))))

(de  :endp ()
     (eq :position :history))


; .Section "Utilitaires"

(setq #:sys-package:colon '#:edlin:util)

(defvar :tampon (makestring 256 #\sp))

(de  :insert-in-string (string pos max cn)
     (if (eq pos max)
         (sset string pos cn)
         (bltstring :tampon 0 string pos (sub max pos))
         (sset string pos cn)
         (bltstring string (add1 pos) :tampon 0 (sub max pos))))

(de  :delete-in-string (string pos max)
     (if (eq pos max)
         (sset string pos (sref string (add1 pos)))
         (bltstring :tampon 0 string (add1 pos) (sub1 (sub max pos)))
         (bltstring string pos :tampon 0 (sub1 (sub max pos)))))


; .Section "Gestion de l'image du tampon d'entre'e"

(setq #:sys-package:colon 'edlin)

(de  :image:redisplay ()
     (tycr) (tycleol) (tyflush)
     (tystring (prompt) (slength (prompt)))
     (:image:redisplay-eol 0))

(de  :image:redisplay-eol (pos)
     (let ((:pos pos))
          (:image:tycleol)
          (:image:move :max))
     (:image:tycleol)
     (setq pos :pos)
     (let ((:pos :max))
          (:image:move pos)))

(de  :image:move (x)
     (cond ((lt :pos x)
            (let ((pos :pos))
                 (until (eq pos x)
                        (tyctl tycn (sref :inbuf pos)) 
                        (setq pos (add1 pos)))))
           ((gt :pos x)
            (let ((pos :pos))
                 (until (eq pos x)
                        (tyctl tybs (sref :inbuf 
                                           (setq pos (sub1 pos)))))))))

(de  :image:insert (cn)
     (if (eq :pos :max)
         (tyctl tycn cn)
         ; on aide les terminaux vt100
         (unless (tyctl tyinsch cn)
                 (:image:redisplay-eol (sub1 :pos)))))

(de  :image:delete (cn)
     (unless (tyctl tydelcn cn)
             (:image:redisplay-eol :pos)))

(de  :image:delete-without-redisplay (cn)
     (tyctl tydelcn cn))

(de  :image:tycleol ()
     (unless (tycleol)
             ; ca marche a peu pres
             ; dans la mesure ou on traite les caracteres un par un
             (tycn #\sp) (tybs #\sp))
     (tyflush))

(dmd tyctl (tyfnt x)
     `(let ((x ,x))
           (when (lt x #\sp)
                 (,tyfnt #/↑)
                 (setq x (logor x #$40)))
           (,tyfnt x)))


; .Section "Edition de ligne a` la Emacs"

(setq #:sys-package:colon 'edlin)

(defvar :inbuf)
; le kill-buffer est global et conserve entre 2 appels a :bol
(defvar :kill-buffer (copy (inbuf)))    ; copie pour wipe & yank
(defvar :kill-length 0)                 ; longueur du yank
(defvar :max)
(defvar :pos)
; pour attendre la parenthese ouvrante (voir insert-rpar)
(defvar :loop 1000)

(de  :bol ()
     (if (or (fixp (inchan))
             (not #:system:real-terminal-flag)
             #:system:line-mode-flag)
         (super-itsoft '#.#:sys-package:colon 'bol ())
         (let ((:inbuf (inbuf))        ; le tampon de lecture
               (:last-string ())
               (:pos 0)        ; la position courante
               (:max 0))
              (tystring (prompt) (slength (prompt)))
              (unless (:history:endp)
                      (:do-command #↑N))
              (untilexit eoi        ; jusqu'a` la fin de l'input
                      (:work-on (tyi)))
              (tynewline)
              (inmax :max)
              (inpos 0))))

(de  :work-on (char)
     (if (and (eq :pos 0) (eq :max 0)
              (or (eq (typecn char) 'cmacro) (eq (typecn char) 'smacro)))
         (:insert char)
         (or (:do-command char) (:insert char))))

(de  :insert (char)
     (:util:insert-in-string :inbuf :pos :max char)
     (setq :pos (add1 :pos))
     (setq :max (add1 :max))
     (:image:insert char)
     (when (ge :max (slength :inbuf))
           (exit eoi)))

(de  :delete (char)
     (:util:delete-in-string :inbuf :pos :max)
     (setq :max (sub1 :max))
     (:image:delete char))

(de  :delete-without-redisplay (char)
     (:util:delete-in-string :inbuf :pos :max)
     (setq :max (sub1 :max))
     (:image:delete-without-redisplay char))

(de  :move (x)
     (:image:move x)
     (setq :pos x))

(de  :do-command (char)
     (block :do-command
         (selectq char
               (#↑A (:move 0))
               (#↑E (:move :max))
               (#↑B (when (gt :pos 0)
                          (:move (sub1 :pos))))
               (#↑F (when (lt :pos :max)
                          (:move (add1 :pos))))
               ((#\cr #\lf)
                    (exit eoi (:store-line) (:history:last)))
               (#↑O (exit eoi (:store-line)))
               ((#↑H 127)
                (when (gt :pos 0)
                      (:move (sub1 :pos))
                      (:delete (sref :inbuf :pos))))
               (#↑D (when (lt :pos :max)
                          (:delete (sref :inbuf :pos))))
               (#↑K (setq :last-string ())
                    (:delete-end-of-line))
               ((#↑X #↑U) (setq :last-string ())
                          (:delete-begining-of-line))
               (#↑Y (:insert-substring :kill-buffer :kill-length))
               (#↑I (:replace-string (:history:previous)))
               (#↑N (:replace-string (:history:next)))
               (#↑T
                (when (gt :pos 1)
                      (let ((char1 (sref :inbuf (sub :pos 2)))
                            (char2 (sref :inbuf (sub :pos 1))))
                           (:move (sub :pos 2))
                           (:delete char1) (:delete char2)
                           (:insert char2) (:insert char1))))
               (#↑L (:redisplay))
               (#/\ (:insert (tyi)))
               (#\esc (:do-esc-command (tyi)))
               (#|(|# #/) (:insert-rpar))
               (t (return-from :do-command ())))
           t ))

(de  :do-esc-command (char)
     (block :do-esc-command
            (selectq char
               (#"0123456789"
                (let ((count (sub char #/0)))
                     (while (memq (setq char (tyi)) '#"0123456789")
                            (setq count (add (sub char #/0) (mul count 10))))
                     (repeat count (:work-on char))))
               (#/?
                (terpri)
                (with ((inchan (openi (catenate #:system:llib-directory
                                          "edhelp"
                                          #:system:lelisp-extension))))
                      (untilexit eof (print (readstring))))
                (terpri)
                (:redisplay))
               (#/b (:backword #↑B))
               (#/f (:forword #↑F))
               ((#↑H #\del) (:backword #↑H))
               (#/h (:history:show)
                    (:redisplay))
               ((#/d #↑D) (:forword #↑D))
               (#↑G (tybeep))
               (#/< (:replace-string (:history:first)))
               (#/> (:replace-string (:history:last)))
               (#\esc
                (let ((s (:history:find
                             ; :last-string est remis a () dans bol et ↑U
                             (or :last-string
                                 (setq :last-string
                                       (substring :inbuf 0 :max))))))
                     (when s (:replace-string s))))
               (#\sp (:find-in-oblist :pos))
               (#/' (let ((pos :pos))
                         (:backword #↑B)
                         (:insert #/')
                         (repeat (sub (add1 pos) :pos) (:do-command #↑F))))
               (#/( #|)|# (:move (:backwardparen :pos)))
               (t (tybeep) (return-from :do-esc-command ())))
           t ))

(de  :find-in-oblist (pos)
     (:backword #↑B)
     (let ((l (sub pos :pos))
           (s (:search-in-oblist :pos
                  (substring :inbuf :pos (sub pos :pos)))))
          (repeat l (:do-command #↑F))
          (cond ((atom s) (tybeep))
                ((null (cdr s))
                 (setq s (string (car s)))
                 (for (i l 1 (sub1 (slength s)))
                      (:insert (sref s i))))
                (t (terpri)
                   (mapc 'print (setq s (sort 'alphalessp s)))
                   (terpri)
                   (setq s (:comm-string s))
                   (:redisplay)
                   (for (i l 1 (sub1 (slength s)))
                        (:insert (sref s i)))))))

(de  :store-line ()
     (when (gt :max 0)
           ; allocation et stockage d'une nouvelle string
           (:history:add (substring :inbuf 0 :max)))
     (sset :inbuf :max #\cr) (setq :max (add1 :max))
     (sset :inbuf :max #\lf) (setq :max (add1 :max)))

(de  :delete-end-of-line ()
     (bltstring :kill-buffer 0 :inbuf :pos (sub :max :pos))
     (setq :kill-length (sub :max :pos))
     (repeat (sub :max :pos)
	     (when (lt :pos :max)
		   (:delete-without-redisplay (sref :inbuf :pos))))
     (:image:redisplay-eol :pos))

(de  :delete-begining-of-line ()
     (bltstring :kill-buffer 0 :inbuf 0 :pos)
     (setq :kill-length :pos)
     (repeat :pos (when (gt :pos 0)
			(:move (sub1 :pos))
			(:delete-without-redisplay (sref :inbuf :pos))))
     (:image:redisplay-eol :pos))

(de  :insert-substring (s l)
     (for (i 0 1 (sub1 (imin l (slength s))))
          (:insert (sref s i))))

(de  :replace-string (s)
     (let ((l (slength s)))
          (bltstring :inbuf 0 s 0 l)
          (setq :max l  :pos :max))
     (:redisplay))

(de  :alphap (char)
     (eq (typecn char) 'cpname))

(de  :backword (com)
     (until (or (eq :pos 0) (:alphap (sref :inbuf (sub1 :pos))))
            (:do-command com))
     (while (and (gt :pos 0) (:alphap (sref :inbuf (sub1 :pos))))
            (:do-command com)))

(de  :forword (com)
     (until (or (eq :pos :max) (:alphap (sref :inbuf :pos)))
            (:do-command com))
     (while (and (lt :pos :max) (:alphap (sref :inbuf :pos)))
            (:do-command com)))

(de  :redisplay ()
     (:image:redisplay))

(de :search-in-oblist (pos string)
    (let (pkgc (npos pos))
         (while (and (gt npos 0)
                     (or (memq (chrnth (sub1 npos) :inbuf) '#":#")
                         (:alphap (chrnth (sub1 npos) :inbuf))))
                (setq npos (sub1 npos)))
         (setq pkgc
               (car (catcherror ()
                        (implode (explode (substring :inbuf
                                             npos (sub (sub1 pos) npos)))))))
         (maploblist
             (lambda (u)
                     (when (and (eq pkgc (packagecell u))
                                (eq 0 (index string (string u) 0)))
                           (ncons u))))))

(de :comm-string (lsymb)
    ; lsymb est trie'e par ordre alphabetique
    (let ((s1 (string (car lsymb)))
          (s2 (string (car (last lsymb)))))
         (let ((i 0) (fin (imin (slength s1) (slength s2))))
              (while (and (lt i fin) (eq (sref s1 i) (sref s2 i)))
                     (setq i (add1 i)))
              (substring s1 0 i))))

(de :insert-rpar ()
    (:insert #|(|# #/))
    (:move (prog1 :pos (:move (:backwardparen :pos)) (tyflush)
                       (repeat :loop (setq :pos :pos)))))


(de :backwardparen (pos)
    (let ((depth 1))
         (when (gt pos 0)
               (decr pos 1)
               (while (and (gt pos 0)
                           (or (neq 'clpar (typecn (sref :inbuf pos)))
                               (gt (decr depth 1) 0)))
                      (decr pos)
                      (or (neq 'crpar (typecn (sref :inbuf pos)))
                          (setq depth (add1 depth))))))
    pos )


; .Section "Les Fonctions Utilisateur"

(de  edlin ()
     (unless (memq '#.#:sys-package:colon #:sys-package:itsoft)
             (setq #:sys-package:itsoft
                   (cons '#.#:sys-package:colon #:sys-package:itsoft))))

(de  edlinend ()
     (setq #:sys-package:itsoft
           (delq '#.#:sys-package:colon #:sys-package:itsoft)))