| [7833] | 1 | ;;; -*- Mode: Lisp; Package: hemlock-internals -*-
|
|---|
| 2 |
|
|---|
| 3 | (in-package :hemlock-internals)
|
|---|
| 4 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|---|
| 5 | ;;
|
|---|
| 6 | ;; A HEMLOCK-VIEW represents text displayed in a (pane inside a) window.
|
|---|
| 7 | ;; Conceptually it consists of a text buffer, a modeline for semi-permanent status
|
|---|
| 8 | ;; info, an echo area for transient status info, and a text input area for reading
|
|---|
| 9 | ;; prompted input. Currently the last two are conflated.
|
|---|
| 10 | ;;
|
|---|
| 11 | ;; A HEMLOCK-VIEW never changes which text buffer it displays (unlike in emacs). A
|
|---|
| 12 | ;; text buffer can be displayed in multiple HEMLOCK-VIEW's, although currently there
|
|---|
| 13 | ;; is no UI to make that happen. But we try take care to distinguish per-buffer info
|
|---|
| 14 | ;; from per-view info. The former is stored in the buffer struct and in buffer
|
|---|
| 15 | ;; variables. The latter is currently stored in HEMLOCK-VIEW slots, although I'd
|
|---|
| 16 | ;; like to introduce user-definable "view variables" and get rid of some of these
|
|---|
| 17 | ;; user-level slots. [Note: currently, multiple views on a buffer are but a remote
|
|---|
| 18 | ;; dream. Basic things like the insertion point are still per buffer when they
|
|---|
| 19 | ;; should be per view]
|
|---|
| [16082] | 20 |
|
|---|
| 21 | ;; NOTE: In CCL, it's no longer a dream. See definition
|
|---|
| 22 | ;; (objc:defmethod (#/duplicate: :void) ((self hemlock-frame) sender)
|
|---|
| [7833] | 23 | ;;
|
|---|
| 24 | ;; The user interacts with a HEMLOCK-VIEW using events. Each time the user presses a
|
|---|
| 25 | ;; key, the OS arranges to invoke our event handler. The event handler computes and
|
|---|
| 26 | ;; invokes a hemlock command bound to the key. The command is invoked in a
|
|---|
| 27 | ;; dynamic context most suitable for modifying the text buffer associated with the
|
|---|
| 28 | ;; HEMLOCK-VIEW, but by jumping through a few hoops, it can modify other buffers.
|
|---|
| 29 |
|
|---|
| 30 | (defvar *current-view* nil)
|
|---|
| 31 |
|
|---|
| [7933] | 32 | (defun current-view (&optional (must-exist t))
|
|---|
| 33 | (or *current-view*
|
|---|
| 34 | (and must-exist (error "Hemlock view context not established"))))
|
|---|
| [7844] | 35 |
|
|---|
| [7833] | 36 | (defclass hemlock-view ()
|
|---|
| [7862] | 37 | ((pane :initarg :pane :reader hemlock-view-pane)
|
|---|
| 38 | (buffer :initarg :buffer :reader hemlock-view-buffer)
|
|---|
| [7833] | 39 | (echo-area-buffer :initarg :echo-area-buffer :reader hemlock-echo-area-buffer)
|
|---|
| 40 | (echo-area-stream :reader hemlock-echo-area-stream)
|
|---|
| 41 |
|
|---|
| 42 | ;; Input state
|
|---|
| 43 | (quote-next-p :initform nil :accessor hemlock-view-quote-next-p)
|
|---|
| 44 | (current-command :initform (make-array 10 :fill-pointer 0 :adjustable t)
|
|---|
| [7993] | 45 | :accessor hemlock-current-command)
|
|---|
| 46 | (last-command :initform (make-array 10 :fill-pointer 0 :adjustable t)
|
|---|
| 47 | :accessor hemlock-last-command)
|
|---|
| [7833] | 48 | (prefix-argument-state :initform (make-prefix-argument-state)
|
|---|
| 49 | :accessor hemlock-prefix-argument-state)
|
|---|
| 50 | ;; If set, events are diverted to the echo area for reading prompt-for-xxx input.
|
|---|
| 51 | (prompted-input-state :initform nil :accessor hemlock-prompted-input-state)
|
|---|
| 52 |
|
|---|
| 53 | (cancel-message :initform nil :accessor hemlock-cancel-message)
|
|---|
| 54 |
|
|---|
| 55 | ;; User level "view variables", for now give each its own slot.
|
|---|
| 56 | (last-command-type :initform nil :accessor hemlock-last-command-type)
|
|---|
| 57 | (target-column :initform 0 :accessor hemlock-target-column)
|
|---|
| 58 | ))
|
|---|
| 59 |
|
|---|
| [15019] | 60 | (defmethod print-object ((view hemlock-view) stream)
|
|---|
| 61 | (print-unreadable-object (view stream :type t :identity t)
|
|---|
| 62 | (if (slot-boundp view 'buffer)
|
|---|
| 63 | (prin1 (buffer-name (hemlock-view-buffer view)) stream)
|
|---|
| 64 | (princ "???" stream))))
|
|---|
| 65 |
|
|---|
| [8207] | 66 | (defun hemlock-view-p (object)
|
|---|
| 67 | (typep object 'hemlock-view))
|
|---|
| 68 |
|
|---|
| [7833] | 69 | (defmethod initialize-instance ((view hemlock-view) &key)
|
|---|
| 70 | (call-next-method)
|
|---|
| 71 | (with-slots (echo-area-buffer echo-area-stream) view
|
|---|
| 72 | (setf echo-area-stream
|
|---|
| 73 | (make-hemlock-output-stream (buffer-end-mark echo-area-buffer) :full))))
|
|---|
| 74 |
|
|---|
| 75 | (defun current-prefix-argument-state ()
|
|---|
| [7933] | 76 | (hemlock-prefix-argument-state (current-view)))
|
|---|
| [7833] | 77 |
|
|---|
| [7844] | 78 | (defun last-key-event-typed ()
|
|---|
| 79 | "This function returns the last key-event typed by the user and read as input."
|
|---|
| [7993] | 80 | (let* ((view (current-view))
|
|---|
| 81 | (keys (hemlock-current-command view)))
|
|---|
| 82 | (when (= (length keys) 0) ;; the normal case, when executing a command.
|
|---|
| 83 | (setq keys (hemlock-last-command view)))
|
|---|
| 84 | (when (> (length keys) 0)
|
|---|
| 85 | (aref keys (1- (length keys))))))
|
|---|
| [7844] | 86 |
|
|---|
| 87 | (defun last-char-typed ()
|
|---|
| [7993] | 88 | (let ((key (last-key-event-typed)))
|
|---|
| 89 | (and key (key-event-char key))))
|
|---|
| [7844] | 90 |
|
|---|
| [7833] | 91 | ;; This handles errors in event handling. It assumes it's called in a normal
|
|---|
| 92 | ;; event handling context for some view.
|
|---|
| [8207] | 93 | (defun lisp-error-error-handler (condition &key debug-p)
|
|---|
| [7844] | 94 | (with-standard-standard-output
|
|---|
| 95 | (handler-case
|
|---|
| [7911] | 96 | (progn
|
|---|
| [8207] | 97 | (hemlock-ext:report-hemlock-error (current-view) condition debug-p)
|
|---|
| [7911] | 98 | (let ((emsg (ignore-errors (princ-to-string condition))))
|
|---|
| 99 | (abort-to-toplevel (or emsg "Error"))))
|
|---|
| [7844] | 100 | (error (cc)
|
|---|
| 101 | (ignore-errors (format t "~&Event error handling failed"))
|
|---|
| 102 | (ignore-errors (format t ": ~a" cc))
|
|---|
| 103 | (abort)))))
|
|---|
| [7833] | 104 |
|
|---|
| 105 |
|
|---|
| 106 | ;; This resets the command accumulation state in the current view.
|
|---|
| 107 | (defmethod reset-command-state ()
|
|---|
| [7933] | 108 | (let ((view (current-view)))
|
|---|
| [7833] | 109 | ;; This resets c-q
|
|---|
| 110 | (setf (hemlock-view-quote-next-p view) nil)
|
|---|
| 111 | ;; This resets c-x (multi-key command) and c-c (modifier prefix command)
|
|---|
| 112 | (setf (fill-pointer (hemlock-current-command view)) 0)
|
|---|
| 113 | ;; This resets the numarg state.
|
|---|
| 114 | (prefix-argument-resetting-state (hemlock-prefix-argument-state view))))
|
|---|
| 115 |
|
|---|
| 116 | ;; This is called for ^G and for lisp errors. It aborts all editor state,
|
|---|
| 117 | ;; including recursive reading input and incremental search.
|
|---|
| 118 | (defun abort-to-toplevel (&optional (message "Cancelled"))
|
|---|
| 119 | ;; This assumes it's called in normal event state.
|
|---|
| 120 | (assert (and *current-view* (find-restart 'exit-event-handler)))
|
|---|
| 121 | (reset-command-state)
|
|---|
| 122 | (invoke-hook hemlock::abort-hook) ;; reset ephemeral modes such as i-search.
|
|---|
| [7933] | 123 | (setf (hemlock-cancel-message (current-view)) message)
|
|---|
| [7833] | 124 | (let ((eps (current-echo-parse-state :must-exist nil)))
|
|---|
| 125 | (when eps
|
|---|
| 126 | (exit-echo-parse eps :aborted)))
|
|---|
| 127 | (exit-event-handler))
|
|---|
| 128 |
|
|---|
| 129 | ;; Called for editor errors. This aborts command accumulation and i-search,
|
|---|
| 130 | ;; but not recursive reading of input.
|
|---|
| 131 | (defun abort-current-command (&optional (message "Cancelled"))
|
|---|
| 132 | ;; This assumes it's called in normal event state.
|
|---|
| 133 | (assert (and *current-view* (find-restart 'exit-event-handler)))
|
|---|
| 134 | (reset-command-state)
|
|---|
| 135 | (invoke-hook hemlock::abort-hook)
|
|---|
| [7933] | 136 | (setf (hemlock-cancel-message (current-view)) message)
|
|---|
| [7833] | 137 | (exit-event-handler))
|
|---|
| 138 |
|
|---|
| 139 | (defun exit-event-handler (&optional message)
|
|---|
| 140 | (when (and *current-view* message)
|
|---|
| 141 | (setf (hemlock-cancel-message *current-view*) message))
|
|---|
| 142 | (let ((restart (find-restart 'exit-event-handler)))
|
|---|
| 143 | (if restart
|
|---|
| 144 | (ccl::invoke-restart-no-return restart)
|
|---|
| 145 | (abort))))
|
|---|
| 146 |
|
|---|
| [7993] | 147 | ;; These are only used in event handling, and as such are serialized
|
|---|
| 148 | (defparameter *translation-temp-1* (make-array 10 :fill-pointer 0 :adjustable t))
|
|---|
| 149 | (defparameter *translation-temp-2* (make-array 10 :fill-pointer 0 :adjustable t))
|
|---|
| 150 |
|
|---|
| 151 | (defmethod translate-and-lookup-command (keys)
|
|---|
| [7833] | 152 | ;; Returns NIL if we're in the middle of a command (either multi-key, as in c-x,
|
|---|
| 153 | ;; or translation prefix, as in ESC for Meta-), else a command.
|
|---|
| 154 | (multiple-value-bind (translated-key prefix-p)
|
|---|
| [7993] | 155 | (translate-key keys *translation-temp-1* *translation-temp-2*)
|
|---|
| [7833] | 156 | (multiple-value-bind (res t-bindings)
|
|---|
| 157 | (get-current-binding translated-key)
|
|---|
| 158 | (etypecase res
|
|---|
| 159 | (command
|
|---|
| 160 | (values res t-bindings))
|
|---|
| 161 | (hash-table ;; we're part-way through a multi-key command
|
|---|
| 162 | nil)
|
|---|
| 163 | (null
|
|---|
| 164 | (if prefix-p ;; we're part-way through a translation prefix
|
|---|
| 165 | nil
|
|---|
| 166 | (values (get-default-command) nil)))))))
|
|---|
| 167 |
|
|---|
| [7993] | 168 |
|
|---|
| [7833] | 169 | ;; This has a side effect of resetting the quoting state and current command.
|
|---|
| 170 | (defmethod get-command-binding-for-key ((view hemlock-view) key)
|
|---|
| [7993] | 171 | (let ((current-keys (hemlock-current-command view)))
|
|---|
| 172 | (vector-push-extend key current-keys)
|
|---|
| 173 | (multiple-value-bind (main-binding t-bindings)
|
|---|
| 174 | (if (shiftf (hemlock-view-quote-next-p view) nil)
|
|---|
| 175 | (values (get-self-insert-command) nil)
|
|---|
| 176 | (let ((eps (hemlock-prompted-input-state view)))
|
|---|
| 177 | (or (and eps (eps-parse-key-handler eps))
|
|---|
| 178 | (translate-and-lookup-command current-keys))))
|
|---|
| 179 | (when main-binding
|
|---|
| 180 | (let ((vec (hemlock-last-command view))) ;; reuse vector
|
|---|
| 181 | (setf (hemlock-last-command view) current-keys)
|
|---|
| 182 | (setf (fill-pointer vec) 0)
|
|---|
| 183 | (setf (hemlock-current-command view) vec))
|
|---|
| 184 | (values main-binding t-bindings)))))
|
|---|
| [7833] | 185 |
|
|---|
| 186 | (defvar *last-last-command-type*)
|
|---|
| 187 | (defvar *last-prefix-argument*)
|
|---|
| 188 |
|
|---|
| [7993] | 189 | (defun invoke-command (command p)
|
|---|
| 190 | (funcall (command-function command) p))
|
|---|
| [7844] | 191 |
|
|---|
| [7833] | 192 | (defmethod execute-hemlock-key ((view hemlock-view) key)
|
|---|
| [10614] | 193 | #+debug (log-debug "~&execute-hemlock-key ~s" key)
|
|---|
| [7993] | 194 | (with-output-to-listener
|
|---|
| 195 | (if (or (symbolp key) (functionp key))
|
|---|
| 196 | (funcall key)
|
|---|
| 197 | (multiple-value-bind (main-binding transparent-bindings)
|
|---|
| 198 | (get-command-binding-for-key view key)
|
|---|
| [10614] | 199 | #+debug (log-debug "~& binding ~s ~s" main-binding transparent-bindings)
|
|---|
| [8207] | 200 | (ring-push key *key-event-history*)
|
|---|
| [11914] | 201 | ;; If the key represents an "alphabetic" character (of which there
|
|---|
| 202 | ;; are about 94000), and the event has no modifiers or only a shift
|
|---|
| 203 | ;; modifier, treat it if it were bound to "Self Insert".
|
|---|
| [12171] | 204 |
|
|---|
| [11914] | 205 | (when (eq main-binding (get-default-command))
|
|---|
| [12315] | 206 | (let* ((modifiers (key-event-bits-modifiers (key-event-bits key)))
|
|---|
| 207 | (char (key-event-char key)))
|
|---|
| 208 | (when (and char
|
|---|
| 209 | (graphic-char-p char)
|
|---|
| [11914] | 210 | (or (null modifiers)
|
|---|
| 211 | (equal '("Shift") modifiers)))
|
|---|
| 212 | (setq main-binding (get-self-insert-command)))))
|
|---|
| [7993] | 213 | (when main-binding
|
|---|
| 214 | (let* ((*last-last-command-type* (shiftf (hemlock-last-command-type view) nil))
|
|---|
| 215 | (*last-prefix-argument* (hemlock::prefix-argument-resetting-state)))
|
|---|
| 216 | (dolist (binding transparent-bindings)
|
|---|
| 217 | (invoke-command binding *last-prefix-argument*))
|
|---|
| 218 | (invoke-command main-binding *last-prefix-argument*)))))))
|
|---|
| [7833] | 219 |
|
|---|
| [12422] | 220 |
|
|---|
| 221 |
|
|---|
| [7833] | 222 | (defmethod update-echo-area-after-command ((view hemlock-view))
|
|---|
| 223 | (let* ((eps (hemlock-prompted-input-state view)))
|
|---|
| 224 | ;;if we're in the process of returning from a recursive parse,
|
|---|
| 225 | ;; don't do anything, let the outer event handle it.
|
|---|
| 226 | (unless (and eps (eps-parse-results eps))
|
|---|
| 227 | (let ((msg (shiftf (hemlock-cancel-message view) nil)))
|
|---|
| 228 | (if msg
|
|---|
| [14253] | 229 | (loud-message "~a" msg)
|
|---|
| [7833] | 230 | ;; Echo command in progress if there is one, unless in a recursive parse
|
|---|
| 231 | (unless eps
|
|---|
| 232 | (let ((cmd (hemlock-current-command view)))
|
|---|
| 233 | (unless (eql 0 (length cmd))
|
|---|
| [7993] | 234 | (let ((cstr (concatenate 'string (pretty-key-string cmd) " ")))
|
|---|
| [7833] | 235 | (message cstr))))))))))
|
|---|
| 236 |
|
|---|
| [7862] | 237 | (defmethod hemlock-view-current-buffer ((view hemlock-view))
|
|---|
| 238 | (if (hemlock-prompted-input-state view)
|
|---|
| 239 | (hemlock-echo-area-buffer view)
|
|---|
| 240 | (hemlock-view-buffer view)))
|
|---|
| 241 |
|
|---|
| [7919] | 242 | (defun buffer-modification-state (buffer)
|
|---|
| 243 | (multiple-value-bind (start end) (buffer-selection-range buffer)
|
|---|
| 244 | (list* (buffer-signature buffer) start end)))
|
|---|
| 245 |
|
|---|
| [7933] | 246 | (defvar *next-view-start* nil)
|
|---|
| 247 |
|
|---|
| 248 | (defun set-scroll-position (how &optional where)
|
|---|
| 249 | "Set the desired scroll position of the current view"
|
|---|
| 250 | (when (markp where)
|
|---|
| [8207] | 251 | (unless (eq (mark-buffer where)
|
|---|
| 252 | (hemlock-view-buffer (current-view)))
|
|---|
| 253 | (error "~s is not a mark in the current view." where))
|
|---|
| [7933] | 254 | (setq where (mark-absolute-position where)))
|
|---|
| 255 | (setf *next-view-start* (cons how where)))
|
|---|
| 256 |
|
|---|
| [7833] | 257 | (defmethod handle-hemlock-event ((view hemlock-view) key)
|
|---|
| 258 | ;; Key can also be a function, in which case it will get executed in the view event context
|
|---|
| [10614] | 259 | #+debug (log-debug "handle-hemlock-event ~s~:[~; (recursive)~]"
|
|---|
| [7911] | 260 | key
|
|---|
| 261 | (and (eq view *current-view*)
|
|---|
| 262 | (eq (hemlock-view-current-buffer view) *current-buffer*)))
|
|---|
| 263 | (if (and (eq view *current-view*)
|
|---|
| 264 | (eq (hemlock-view-current-buffer view) *current-buffer*))
|
|---|
| 265 | ;; KLUDGE: This might happen with stuff that normally switches buffers (e.g. meta-.)
|
|---|
| 266 | ;; but happens not to. Because of the stupid buffer binding/unbinding, it's currently
|
|---|
| 267 | ;; problematic to just recurse here, so don't.
|
|---|
| 268 | (progn
|
|---|
| 269 | ;; TODO: should this catch exit-event or let outer one do it? Check callers.
|
|---|
| 270 | (execute-hemlock-key view key)
|
|---|
| 271 | )
|
|---|
| 272 | (ccl::with-standard-abort-handling "Abort editor event handling"
|
|---|
| 273 | (let* ((*current-view* view)
|
|---|
| 274 | (*current-buffer* (hemlock-view-current-buffer view))
|
|---|
| [7933] | 275 | (*next-view-start* nil) ;; gets set by scrolling commands
|
|---|
| [7919] | 276 | (text-buffer (hemlock-view-buffer view))
|
|---|
| 277 | (mod (buffer-modification-state text-buffer)))
|
|---|
| [8062] | 278 | (modifying-buffer-storage (*current-buffer*)
|
|---|
| 279 | (restart-case
|
|---|
| [8207] | 280 | (handler-bind ((error #'(lambda (c)
|
|---|
| 281 | (lisp-error-error-handler c :debug-p t))))
|
|---|
| [8062] | 282 | (execute-hemlock-key view key))
|
|---|
| 283 | (exit-event-handler () :report "Exit from hemlock event handler")))
|
|---|
| 284 | ;; Update display
|
|---|
| 285 | (if *next-view-start*
|
|---|
| 286 | (destructuring-bind (how . where) *next-view-start*
|
|---|
| 287 | (hemlock-ext:scroll-view view how where))
|
|---|
| 288 | (unless (equal mod (buffer-modification-state text-buffer))
|
|---|
| 289 | ;; Modified buffer, make sure user sees what happened
|
|---|
| 290 | (hemlock-ext:ensure-selection-visible view)))
|
|---|
| 291 | (update-echo-area-after-command view)))))
|
|---|