; -*- Mode:LISP; Package:STEVE; Readtable:CL; Base:10 -*- ;Copyright (c) May 1983 by Christopher Eliot ; and Massachusetts Institute of Technology. ;Permission to copy all or part of this material is granted, provided ; that the copies are not made or distributed for resale, the MIT ; copyright notice and reference to the source file and the software ; distribution version appear, and that notice is given that copying ; is by permission of Massachusetts Institute of Technology. ; ;Note: careful about the HOME-LINE being deleted. ; ;These two variables contain most of the state of the editor. (defvar *editor-cursor* nil) (defvar *editor-buffer* nil) ; (defvar *.* nil) ;Copy of *editor-cursor* (defvar *b* nil) ;Copy of *editor-buffer* (defvar *tty-width* 80) ;These are re-set whenever the editor is entered. (defvar *tty-height* 24) ;But might as well make them reasonable for testing. (defvar *tty-init-flag* t) (defvar *last-line*) (defvar *mode-area-height* 4) (defvar *window-height*) (defvar *first-mode-line*) (defvar *last-mode-line* (1-& *mode-area-height*)) (defvar *mode-area*) (defvar *argument*) ;#\control-u argument here. (defvar argument-supplied?) (defvar *last-buffer-selected* nil) ;Used by #\control-x #\b (defvar *error-state* nil) ;Set when an error occurs. Makes editor careful. (defvar *all-edit-cursors* nil) (defvar *all-buffers* nil) (defvar *old-screen-image) (defvar *recentering-fraction* 3/8) (defvar *query-line* 13) (defvar *notify-line* 12) (defvar *feedback-line* 14) (defvar *double-line*) ;Guaranteed to have a line after it. (defvar *error-line* 15) (defvar *prefix-echo-line*) (defvar *first-echo-line*) (defvar *last-overwrite-line* 10) (defvar *more-line* 11) ;Initialized so SAVE-ALL-FILES works outside editor. (defvar *last-buffer-line*) (defvar *current-buffer-modified* nil) (defvar *context-change-flag*) (defvar *editor-current-key*) (defvar *editor-last-command*) (defvar *last-home-line*) (defvar re-generate-screen-really) (defvar re-generate-all-windows-really) (defvar *editor-command-count* 0) (defvar *editor-device-mode*) (defvar *editor-running*) (defvar *major-mode*) (defvar *last-major-mode* nil) (defvar *minor-modes* nil) (defvar *buffer-name-completion-on-return*) (defconstant buffer-access-any 0) (defconstant buffer-access-file-read-only 1) (defconstant buffer-access-buffer-read-only 2) ; ;The top level stuff. ; (defun error-ed () (oustr ";Attempting to save from editor error" terminal-io) (send *editor-cursor* :move (buffer-content *editor-buffer*) 0) (when (null (edit-cursor-window *editor-cursor*)) (setf (edit-cursor-window *editor-cursor*) (create-window 0 0 80 19))) (ed)) ;;;Do not try to display the buffer inside this function. It will be done ;;;in the wrong environment. (defun editor-initialize (spec) (when (null *editor-cursor*) (initial-editor-state)) (cond ((null spec)) ((symbolp spec) (find-function spec)) ((and (listp spec) ;This is a hack for teach-steve (eq (first spec) :load-and-rename)) (let ((point (point-selected (pathname (second spec)) :create t))) (send (bp-buffer point) :set-file-name (pathname (third spec))))) ((stringp spec) (point-selected (pathname spec) :create t)) ((point-selected spec :create t)) (t (format t "%&Invalid argument")))) (defun initial-editor-state () (let* ((curse (make-point "Main" (create-window 0 0 *tty-width* *window-height*))) (buff (edit-cursor-buffer curse))) (setq *editor-buffer* buff *b* buff *last-buffer-selected* buff *editor-cursor* curse *.* curse))) (defparameter *steve-tutorial-file* "nil$disk:[nil.steve]teach.txt") (defun teach-steve () (cond ((null (probe-file *steve-tutorial-file*)) (format t "%&Sorry, the STEVE tutorial file is nowhere to be found.") nil) (t (ed (list :load-and-rename *steve-tutorial-file* (fs:merge-pathnames "teach.txt" (user-homedir-pathname))))))) ;;; Synonyms (defun teachemacs () (teach-steve)) (defun teach () (teach-steve)) ;;; The usual way to invoke STEVE (defun ed (&optional (spec nil)) (when (or (null *editor-cursor*) (not (=& *tty-width* (send terminal-io :linel))) (not (=& *tty-height* (send terminal-io :pagel)))) (setq *tty-init-flag* t) (setq *tty-height* (send terminal-io :pagel)) (setq *tty-width* (send terminal-io :linel))) (let* ((*editor-running* t) (re-generate-screen-really t) (re-generate-all-windows-really t) (*mode-area-height* (/& *tty-height* 6)) (*last-line* (1-& *tty-height*)) (*last-overwrite-line* (-& *tty-height* *mode-area-height* 1)) (*more-line* *last-line*) (*last-buffer-line* (1+& (-& *tty-height* *mode-area-height* 2))) (*first-mode-line* (-& *tty-height* *mode-area-height*)) (*last-mode-line* (1-& *mode-area-height*)) (*window-height* (-& *tty-height* *mode-area-height*)) (*first-echo-line* (+& 1 *first-mode-line*)) (*query-line* (+& 0 *first-echo-line*)) (*notify-line* (+& 1 *first-echo-line*)) (*feedback-line* (+& 1 *first-echo-line*)) (*double-line* (+& 1 *first-echo-line*)) (*error-line* (+& 2 *first-echo-line*)) (*prefix-echo-line* (+& 2 *first-echo-line*)) (*editor-current-key* nil) (*editor-last-command* nil) (*context-change-flag* nil) (*argument* 1) (argument-supplied? nil) (*last-home-line* nil) ;;Make sure echo window is set up. Use it for Standard-output. (echo-area-window (setup-echo-area-window)) (standard-output echo-area-window) (error-output echo-area-window) (trace-output echo-area-window) (si:print-gc-messages nil)) (when (not (null *tty-init-flag*)) (init-tty-for-editor) (setq *tty-init-flag* nil)) (when (not (boundp '*editor-cursor*)) (setq *editor-cursor* nil)) (when (not (boundp '*editor-buffer*)) (setq *editor-buffer* nil)) (editor-guts spec) '*)) (deff steve #'ed) (defvar *terminal-passall-on-exit*) (defvar *terminal-ttsync-on-exit*) (defun editor-guts (&optional spec) (let ((*terminal-passall-on-exit* (send terminal-io :get-device-mode :passall)) ;;We want to restore these on exit. (*terminal-ttsync-on-exit* (send terminal-io :get-device-mode :ttysync))) (unwind-protect (catch 'exit-editor ;We are trying to prevent the user from typing control-s as ;the editor turns on passall mode, (thus getting screwed completely.) ;If control-s is typed as the editor is being entered, it should ;cause this WRITE-CHAR to hang, until a control-q is typed. ;It is possible to type control-q between then and the time ttsync is ;turned off. In that case output will hang on the :CLEAR-SCREEN below. ;When this happens the terminal is not in passall mode, but ;ttsync is off. One may still exit with control-c or control-y. (send terminal-io :write-char #\space) (send terminal-io :set-device-mode :ttysync nil) (send terminal-io :clear-screen) ;See note above. (send terminal-io :set-device-mode :passall *editor-device-mode*) (clear-one-screen-image old-screen-image) (unless (null *terminal-passall-on-exit*) (with-notify-line-remaining (oustr "PASSALL mode on" terminal-io))) (catching-editor-losses (editor-initialize spec)) (loop do (when (null (buffer-environment *editor-buffer*)) (initialize-buffer-environment *editor-buffer*)) do (process-in-saved-buffer-environment (buffer-environment *editor-buffer*) #'editor-top-level (vector)))) ;;Restore TTY device modes. (unless (null *terminal-passall-on-exit*) (send terminal-io :set-cursorpos 0 0) (send terminal-io :clear-eol) (oustr "Passall mode left on" terminal-io)) (send terminal-io :set-device-mode :passall *terminal-passall-on-exit*) (send terminal-io :set-device-mode :ttysync *terminal-ttsync-on-exit*) ;;Patch mode line. (editor-destroy-mode-line-for-exiting) ))) (defun editor-destroy-mode-line-for-exiting () (cursorpos *first-mode-line* 0 terminal-io) (oustr " " terminal-io) (cursorpos (1+& *first-mode-line*) 0 terminal-io) (cursorpos 'end-of-screen terminal-io)) (defun editor-top-level (&optional gunk-pathname) (setq *context-change-flag* nil) (setup-mode-area) (loop while (null *context-change-flag*) do (setq executing-keyboard-macro nil) do (setq save-chars-for-keyboard-macros nil) ;;do (setq keyboard-macro-char-list nil) do (when (memq '|Def| *minor-modes*) (setq *minor-modes* (delq '|Def| *minor-modes*)) (setup-mode-area)) do (catch :editor-loss (progn (catch 'si:errset-catch (editor-top-level-loop)) (unless *context-change-flag* (setq *last-major-mode* "No mode") (setq creamed-tty-lines-to (max& creamed-tty-lines-to (1+& (car (cursorpos terminal-io))))) (ed-lose "Quit to Editor")))))) (defun editor-top-level-loop () (send terminal-io :set-device-mode :passall *editor-device-mode*) (loop while (null *context-change-flag*) do (ed-command-interpret))) (defmacro self-insert? (chr) `(and (graphic-charp ,chr) (case (lookup-key-in-table ,chr *editor-bindings*) ('self-insert t) ((nil) (lookup-key-in-table (char-upcase ,chr) *editor-bindings*))))) (defun ed-command-interpret (&aux key cmnd-fun) (setq *argument* 1 auto-digit-arg-save nil argument-supplied? nil) (when (or (neq *current-buffer-modified* (buffer-modified? *editor-buffer*)) (neq *last-major-mode* *major-mode*)) (setup-mode-area)) (make-screen-image) (when (and (null (type-ahead-p)) (or (send *editor-cursor* :end-of-line?) *overwrite-mode*) (buffer-modified? *editor-buffer*) (0p (logand& (buffer-access *editor-buffer*) buffer-access-buffer-read-only)) (null save-chars-for-keyboard-macros)) (let ((string (editor-echoin (send (edit-cursor-window *editor-cursor*) :make-cursor-visible) (send *editor-bindings* :get-echoin-bit-vector))) (length) (x) (y)) (unless (null string) (setq *editor-command-count* (1+& *editor-command-count*) *editor-last-command* 'echoin-string) (setq length (length (setq string (coerce string 'string)))) (multiple-value (x y) (edit-cursor-position-really *editor-cursor*)) (send *editor-cursor* :insert-string string) (%string-replace (svref old-screen-image (1-& (send terminal-io :linenum))) string x 0 length))) (make-screen-image)) (or (type-ahead-p) (send (edit-cursor-window *editor-cursor*) :make-cursor-visible)) (multiple-value (key cmnd-fun) (read-key)) (if (null cmnd-fun) (ed-warn :undefined-key)) (setq *editor-current-key* key) (setq *editor-command-count* (1+& *editor-command-count*)) (when (not (null (funcall cmnd-fun))) ;Actually execute a command. (ed-warn :redisplay-advice-returned)) (setq *editor-last-command* cmnd-fun)) (defun funcall-key (key) (let ((command (internal-editor-lookup-key key))) (when (consp command) (ed-lose "Can't funcall-key macro key-binding")) (funcall command))) (defun setup-mode-area () (let ((mode-line (with-output-to-string (mode-line) (if (null recursive-edit) (oustr "STEVE " mode-line) (format mode-line "[STEVE ~a] " recursive-edit)) (format mode-line "~a ~a " (buffer-name *editor-buffer*) (cons *major-mode* *minor-modes*)) (let ((filename (buffer-file-name *editor-buffer*)) (version)) (cond ((null filename) (oustr "--No File--" mode-line)) (t (oustr (send filename :string-for-editor) mode-line) (when (and (setq version (buffer-truename *editor-buffer*)) (setq version (send version :version))) (format mode-line " (~d)" version))))) (if (buffer-modified? *editor-buffer*) (oustr " *" mode-line) (oustr " " mode-line))))) (setq *current-buffer-modified* (buffer-modified? *editor-buffer*)) (setq *last-major-mode* *major-mode*) (send terminal-io :send-if-handles :standout t) (cursorpos *first-mode-line* 0 terminal-io) (let ((width (send terminal-io :width))) (send terminal-io :oustr mode-line 0 (min width (string-length mode-line))) (send terminal-io :oustr 80spaces 0 (-& width (send terminal-io :hpos))) (send terminal-io :clear-eol) (send terminal-io :send-if-handles :standout nil)))) ;;; The beginnings of competent error handling... (defmacro def-ed-error (symbol message &optional feep?) `(progn (defprop ,symbol ,message error-message) (defprop ,symbol ,feep? error-beep?))) (def-ed-error :move-past-start "Attempt to move past beginning of buffer" :beep) (def-ed-error :move-past-end "Attempt to move past end of buffer" :beep) (def-ed-error :at-start "At beginning of buffer") (def-ed-error :at-end "At end of buffer") (def-ed-error :at-first-line "At first line") (def-ed-error :at-last-line "At last line") (def-ed-error :no-mark "No mark" :beep) (def-ed-error :read-only "Buffer is read only" :beep) (def-ed-error :undefined-key "Undefined key" :beep) (def-ed-error :unbalanced-parens "Unbalanced parentheses" :beep) (def-ed-error :aborted "Aborted" :beep) (def-ed-error :redisplay-adviced-returned "Redisplay advice returned") ;;;;;;;;;; (defmacro catching-editor-losses (&body forms) `(catch :editor-loss (progn ,@forms))) (defun ed-warn (error &REST args &aux message) (with-error-line-remaining (cond ((and (symbolp error) (setq message (get error 'error-message))) (when (get error 'error-beep?) (steve-beep)) (format terminal-io message)) (:else (steve-beep) (lexpr-funcall #'format terminal-io error args)))) (setq *error-state* t) (throw :editor-loss t)) (defun ed-abort (&optional echop (stream standard-output)) (when echop (oustr "^G" stream)) (steve-beep) (throw :editor-loss t)) (defun ed-lose (format-string &restv args) ;Temporarily reset passall mode in case of multiple errors. (send terminal-io :set-device-mode :passall nil) (with-error-line-remaining (format terminal-io "Error: ") (lexpr-funcall #'format terminal-io format-string args) (clear-input terminal-io) (setq pushback-flag nil) (setq *error-state* t) ;Don't count upon this doing anything. (throw :editor-loss t))) ;;; I think this is obsolete (defun ed-warning (format-string &REST args) (with-error-line-remaining (lexpr-funcall #'format terminal-io format-string args)))