Changeset 592
- Timestamp:
- Feb 28, 2004, 5:36:58 PM (21 years ago)
- Location:
- trunk/ccl/examples
- Files:
-
- 2 edited
-
cocoa-editor.lisp (modified) (5 diffs)
-
cocoa-listener.lisp (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/ccl/examples/cocoa-editor.lisp
r569 r592 6 6 (eval-when (:compile-toplevel :load-toplevel :execute) 7 7 (require "COCOA-WINDOW") 8 (require "HEMLOCK-TEXTSTORAGE")) 9 10 (declaim 11 (special *open-editor-documents* *open-editor-documents-lock*) 12 (type list *open-editor-documents*) 13 (type lock *open-editor-documents-lock*)) 14 15 16 (defstruct cocoa-editor-info 17 (document nil) ; the NSDocument 18 (controller nil) ; the NSWindowController (maybe). 19 (listener nil) ; True (a lisp process) if a listener 20 (modeline-plist nil) ; info from attribute line 21 ) 22 23 8 (require "HEMLOCK")) 9 10 (eval-when (:compile-toplevel :execute) 11 (use-interface-dir :cocoa)) 12 13 (defun make-hemlock-buffer (&rest args) 14 (let* ((buf (apply #'hi::make-buffer args))) 15 (or buf 16 (progn 17 (format t "~& couldn't make hemlock buffer with args ~s" args) 18 (dbg) 19 nil)))) 20 21 ;;; Define some key event modifiers. 22 23 ;;; HEMLOCK-EXT::DEFINE-CLX-MODIFIER is kind of misnamed; we can use 24 ;;; it to map NSEvent modifier keys to key-event modifiers. 25 26 (hemlock-ext::define-clx-modifier #$NSShiftKeyMask "Shift") 27 (hemlock-ext::define-clx-modifier #$NSControlKeyMask "Control") 28 (hemlock-ext::define-clx-modifier #$NSAlternateKeyMask "Meta") 29 (hemlock-ext::define-clx-modifier #$NSAlphaShiftKeyMask "Lock") 30 31 32 ;;; We want to display a Hemlock buffer in a "pane" (an on-screen 33 ;;; view) which in turn is presented in a "frame" (a Cocoa window). A 34 ;;; 1:1 mapping between frames and panes seems to fit best into 35 ;;; Cocoa's document architecture, but we should try to keep the 36 ;;; concepts separate (in case we come up with better UI paradigms.) 37 ;;; Each pane has a modeline (which describes attributes of the 38 ;;; underlying document); each frame has an echo area (which serves 39 ;;; to display some commands' output and to provide multi-character 40 ;;; input.) 41 42 43 ;;; I'd pretty much concluded that it wouldn't be possible to get the 44 ;;; Cocoa text system (whose storage model is based on NSString 45 ;;; NSMutableAttributedString, NSTextStorage, etc.) to get along with 46 ;;; Hemlock, and (since the whole point of using Hemlock was to be 47 ;;; able to treat an editor buffer as a rich lisp data structure) it 48 ;;; seemed like it'd be necessary to toss the higher-level Cocoa text 49 ;;; system and implement our own scrolling, redisplay, selection 50 ;;; ... code. 51 ;;; 52 ;;; Mikel Evins pointed out that NSString and friends were 53 ;;; abstract classes and that there was therefore no reason (in 54 ;;; theory) not to implement a thin wrapper around a Hemlock buffer 55 ;;; that made it act like an NSString. As long as the text system can 56 ;;; ask a few questions about the NSString (its length and the 57 ;;; character and attributes at a given location), it's willing to 58 ;;; display the string in a scrolling, mouse-selectable NSTextView; 59 ;;; as long as Hemlock tells the text system when and how the contents 60 ;;; of the abstract string changes, Cocoa will handle the redisplay 61 ;;; details. 62 ;;; 63 64 65 66 ;;; Hemlock-buffer-string objects: 67 68 (defclass hemlock-buffer-string (ns:ns-string) 69 ((cache :initform nil :initarg :cache :accessor hemlock-buffer-string-cache)) 70 (:metaclass ns:+ns-object)) 71 72 ;;; Cocoa wants to treat the buffer as a linear array of characters; 73 ;;; Hemlock wants to treat it as a doubly-linked list of lines, so 74 ;;; we often have to map between an absolute position in the buffer 75 ;;; and a relative position on a line. We can certainly do that 76 ;;; by counting the characters in preceding lines every time that we're 77 ;;; asked, but we're often asked to map a sequence of nearby positions 78 ;;; and wind up repeating a lot of work. Caching the results of that 79 ;;; work seems to speed things up a bit in many cases; this data structure 80 ;;; is used in that process. (It's also the only way to get to the 81 ;;; actual underlying Lisp buffer from inside the network of text-system 82 ;;; objects.) 83 84 (defstruct buffer-cache 85 buffer ; the hemlock buffer 86 buflen ; length of buffer, if known 87 workline ; cache for character-at-index 88 workline-offset ; cached offset of workline 89 workline-length ; length of cached workline 90 ) 91 92 ;;; Initialize (or reinitialize) a buffer cache, so that it points 93 ;;; to the buffer's first line (which is the only line whose 94 ;;; absolute position will never change). Code which modifies the 95 ;;; buffer generally has to call this, since any cached information 96 ;;; might be invalidated by the modification. 97 (defun reset-buffer-cache (d &optional (buffer (buffer-cache-buffer d) 98 buffer-p)) 99 (when buffer-p (setf (buffer-cache-buffer d) buffer)) 100 (let* ((workline (hemlock::mark-line 101 (hemlock::buffer-start-mark buffer)))) 102 (setf (buffer-cache-buflen d) (hemlock-buffer-length buffer) 103 (buffer-cache-workline-offset d) 0 104 (buffer-cache-workline d) workline 105 (buffer-cache-workline-length d) (hemlock::line-length workline)) 106 d)) 107 108 109 ;;; Update the cache so that it's describing the current absolute 110 ;;; position. 111 (defun update-line-cache-for-index (cache index) 112 (let* ((line (or 113 (buffer-cache-workline cache) 114 (progn 115 (reset-buffer-cache cache) 116 (buffer-cache-workline cache)))) 117 (pos (buffer-cache-workline-offset cache)) 118 (len (buffer-cache-workline-length cache)) 119 (moved nil)) 120 (loop 121 (when (and (>= index pos) 122 (< index (1+ (+ pos len)))) 123 (let* ((idx (- index pos))) 124 (when moved 125 (setf (buffer-cache-workline cache) line 126 (buffer-cache-workline-offset cache) pos 127 (buffer-cache-workline-length cache) len)) 128 (return (values line idx)))) 129 (setq moved t) 130 (if (< index pos) 131 (setq line (hemlock::line-previous line) 132 len (hemlock::line-length line) 133 pos (1- (- pos len))) 134 (setq line (hemlock::line-next line) 135 pos (1+ (+ pos len)) 136 len (hemlock::line-length line)))))) 137 138 ;;; Ask Hemlock to count the characters in the buffer. 139 (defun hemlock-buffer-length (buffer) 140 (hemlock::count-characters (hemlock::buffer-region buffer))) 141 142 ;;; Find the line containing (or immediately preceding) index, which is 143 ;;; assumed to be less than the buffer's length. Return the character 144 ;;; in that line or the trailing #\newline, as appropriate. 145 (defun hemlock-char-at-index (cache index) 146 (multiple-value-bind (line idx) (update-line-cache-for-index cache index) 147 (let* ((len (hemlock::line-length line))) 148 (if (< idx len) 149 (hemlock::line-character line idx) 150 #\newline)))) 151 152 ;;; Given an absolute position, move the specified mark to the appropriate 153 ;;; offset on the appropriate line. 154 (defun move-hemlock-mark-to-absolute-position (mark cache abspos) 155 (multiple-value-bind (line idx) (update-line-cache-for-index cache abspos) 156 (hemlock::move-to-position mark idx line))) 157 158 ;;; Return the absolute position of the mark in the containing buffer. 159 ;;; This doesn't use the caching mechanism, so it's always linear in the 160 ;;; number of preceding lines. 161 (defun mark-absolute-position (mark) 162 (let* ((pos (hemlock::mark-charpos mark))) 163 (do* ((line (hemlock::line-previous (hemlock::mark-line mark)) 164 (hemlock::line-previous line))) 165 ((null line) pos) 166 (incf pos (1+ (hemlock::line-length line)))))) 167 168 ;;; Return the length of the abstract string, i.e., the number of 169 ;;; characters in the buffer (including implicit newlines.) 170 (define-objc-method ((:unsigned length) 171 hemlock-buffer-string) 172 (let* ((cache (hemlock-buffer-string-cache self))) 173 (or (buffer-cache-buflen cache) 174 (setf (buffer-cache-buflen cache) 175 (hemlock-buffer-length (buffer-cache-buffer cache)))))) 176 177 178 ;;; Return the character at the specified index (as a :unichar.) 179 (define-objc-method ((:unichar :character-at-index (unsigned index)) 180 hemlock-buffer-string) 181 (char-code (hemlock-char-at-index (hemlock-buffer-string-cache self) index))) 182 183 184 ;;; Return an NSData object representing the bytes in the string. If 185 ;;; the underlying buffer uses #\linefeed as a line terminator, we can 186 ;;; let the superclass method do the work; otherwise, we have to 187 ;;; ensure that each line is terminated according to the buffer's 188 ;;; conventions. 189 (define-objc-method ((:id :data-using-encoding (:<NSS>tring<E>ncoding encoding) 190 :allow-lossy-conversion (:<BOOL> flag)) 191 hemlock-buffer-string) 192 (let* ((buffer (buffer-cache-buffer (hemlock-buffer-string-cache self))) 193 (external-format (if buffer (hi::buffer-external-format buffer ))) 194 (raw-length (if buffer (hemlock-buffer-length buffer) 0))) 195 (if (eql 0 raw-length) 196 (make-objc-instance 'ns:ns-mutable-data :with-length 0) 197 (case external-format 198 ((:unix nil) 199 (send-super :data-using-encoding encoding :allow-lossy-conversion flag)) 200 ((:macos :cp/m) 201 (let* ((cp/m-p (eq external-format :cp/m))) 202 (when cp/m-p 203 ;; This may seem like lot of fuss about an ancient OS and its 204 ;; odd line-termination conventions. Of course, I'm actually 205 ;; referring to CP/M-86. 206 (do* ((line (hi::mark-line (hi::buffer-start-mark buffer)) 207 next) 208 (next (hi::line-next line) (hi::line-next line))) 209 ((null line)) 210 (when next (incf raw-length)))) 211 (let* ((pos 0) 212 (data (make-objc-instance 'ns:ns-mutable-data 213 :with-length raw-length)) 214 (bytes (send data 'mutable-bytes))) 215 (do* ((line (hi::mark-line (hi::buffer-start-mark buffer)) 216 next) 217 (next (hi::line-next line) (hi::line-next line))) 218 ((null line) data) 219 (let* ((chars (hi::line-chars line)) 220 (len (length chars))) 221 (unless (zerop len) 222 (%copy-ivector-to-ptr chars 0 bytes pos len) 223 (incf pos len)) 224 (when next 225 (setf (%get-byte bytes pos) (char-code #\return)) 226 (when cp/m-p 227 (incf pos) 228 (setf (%get-byte bytes pos) (char-code #\linefeed)) 229 (incf pos)))))))))))) 230 231 232 ;;; For debugging, mostly: make the printed representation of the string 233 ;;; referenence the named Hemlock buffer. 234 (define-objc-method ((:id description) 235 hemlock-buffer-string) 236 (let* ((cache (hemlock-buffer-string-cache self)) 237 (b (buffer-cache-buffer cache))) 238 (with-cstrs ((s (format nil "~a" b))) 239 (send (@class ns-string) :string-with-format #@"<%s for %s>" 240 (:address (#_object_getClassName self) :address s))))) 241 242 243 244 245 ;;; Lisp-text-storage objects 246 (defclass lisp-text-storage (ns:ns-text-storage) 247 ((string :foreign-type :id) 248 (defaultattrs :foreign-type :id)) 249 (:metaclass ns:+ns-object)) 250 251 ;;; Access the string. It'd be nice if this was a generic function; 252 ;;; we could have just made a reader method in the class definition. 253 (define-objc-method ((:id string) lisp-text-storage) 254 (slot-value self 'string)) 255 256 (define-objc-method ((:id :init-with-string s) lisp-text-storage) 257 (let* ((newself (send-super 'init))) 258 (setf (slot-value newself 'string) s 259 (slot-value newself 'defaultattrs) (create-text-attributes)) 260 newself)) 261 262 ;;; This is the only thing that's actually called to create a 263 ;;; lisp-text-storage object. (It also creates the underlying 264 ;;; hemlock-buffer-string.) 265 (defun make-textstorage-for-hemlock-buffer (buffer) 266 (make-objc-instance 'lisp-text-storage 267 :with-string 268 (make-instance 269 'hemlock-buffer-string 270 :cache 271 (reset-buffer-cache 272 (make-buffer-cache) 273 buffer)))) 274 275 ;;; So far, we're ignoring Hemlock's font-marks, so all characters in 276 ;;; the buffer are presumed to have default attributes. 277 (define-objc-method ((:id :attributes-at-index (:unsigned index) 278 :effective-range ((* :<NSR>ange) rangeptr)) 279 lisp-text-storage) 280 (declare (ignorable index)) 281 (let* ((buffer-cache (hemlock-buffer-string-cache (slot-value self 'string))) 282 (len (buffer-cache-buflen buffer-cache))) 283 (unless (%null-ptr-p rangeptr) 284 (setf (pref rangeptr :<NSR>ange.location) 0 285 (pref rangeptr :<NSR>ange.length) len)) 286 (slot-value self 'defaultattrs))) 287 288 ;;; The range's origin should probably be the buffer's point; if 289 ;;; the range has non-zero length, we probably need to think about 290 ;;; things harder. 291 (define-objc-method ((:void :replace-characters-in-range (:<NSR>ange r) 292 :with-string string) 293 lisp-text-storage) 294 (#_NSLog #@"replace-characters-in-range (%d %d) with-string %@" 295 :unsigned (pref r :<NSR>ange.location) 296 :unsigned (pref r :<NSR>ange.length) 297 :id string)) 298 299 ;;; I'm not sure if we want the text system to be able to change 300 ;;; attributes in the buffer. 301 (define-objc-method ((:void :set-attributes attributes 302 :range (:<NSR>ange r)) 303 lisp-text-storage) 304 (#_NSLog #@"set-attributes %@ range (%d %d)" 305 :id attributes 306 :unsigned (pref r :<NSR>ange.location) 307 :unsigned (pref r :<NSR>ange.length))) 308 309 310 ;;; Again, it's helpful to see the buffer name when debugging. 311 (define-objc-method ((:id description) 312 lisp-text-storage) 313 (send (@class ns-string) :string-with-format #@"%s : string %@" 314 (:address (#_object_getClassName self) :id (slot-value self 'string)))) 315 316 (defun close-hemlock-textstorage (ts) 317 (let* ((string (slot-value ts 'string))) 318 (setf (slot-value ts 'string) (%null-ptr)) 319 (unless (%null-ptr-p string) 320 (let* ((cache (hemlock-buffer-string-cache string)) 321 (buffer (if cache (buffer-cache-buffer cache)))) 322 (when buffer 323 (setf (buffer-cache-buffer cache) nil 324 (slot-value string 'cache) nil 325 (hi::buffer-document buffer) nil) 326 (let* ((p (hi::buffer-process buffer))) 327 (when p 328 (setf (hi::buffer-process buffer) nil) 329 (process-kill p))) 330 (when (eq buffer hi::*current-buffer*) 331 (setf (hi::current-buffer) 332 (car (last hi::*buffer-list*)))) 333 (hi::invoke-hook (hi::buffer-delete-hook buffer) buffer) 334 (hi::invoke-hook hemlock::delete-buffer-hook buffer) 335 (setq hi::*buffer-list* (delq buffer hi::*buffer-list*)) 336 (hi::delete-string (hi::buffer-name buffer) hi::*buffer-names*)))))) 337 338 339 340 341 342 343 ;;; A specialized NSTextView. Some of the instance variables are intended 344 ;;; to support paren highlighting by blinking, but that doesn't work yet. 345 ;;; The NSTextView is part of the "pane" object that displays buffers. 346 (defclass hemlock-text-view (ns:ns-text-view) 347 ((timer :foreign-type :id :accessor blink-timer) 348 (blink-pos :foreign-type :int :accessor blink-pos) 349 (blink-phase :foreign-type :<BOOL> :accessor blink-phase) 350 (blink-char :foreign-type :int :accessor blink-char) 351 (pane :foreign-type :id :accessor text-view-pane)) 352 (:metaclass ns:+ns-object)) 353 354 ;;; Access the underlying buffer in one swell foop. 355 (defmethod text-view-buffer ((self hemlock-text-view)) 356 (buffer-cache-buffer (hemlock-buffer-string-cache (send (send self 'text-storage) 'string)))) 357 358 ;;; Translate a keyDown NSEvent to a Hemlock key-event. 359 (defun nsevent-to-key-event (nsevent) 360 (let* ((unmodchars (send nsevent 'characters-ignoring-modifiers)) 361 (n (if (%null-ptr-p unmodchars) 362 0 363 (send unmodchars 'length))) 364 (c (if (eql n 1) 365 (send unmodchars :character-at-index 0)))) 366 (when c 367 (let* ((bits 0) 368 (modifiers (send nsevent 'modifier-flags)) 369 (useful-modifiers (logandc2 modifiers 370 (logior #$NSShiftKeyMask 371 #$NSAlphaShiftKeyMask)))) 372 (dolist (map hemlock-ext::*modifier-translations*) 373 (when (logtest useful-modifiers (car map)) 374 (setq bits (logior bits (hemlock-ext::key-event-modifier-mask 375 (cdr map)))))) 376 (hemlock-ext::make-key-event c bits))))) 377 378 ;;; Process a key-down NSEvent in a lisp text view by translating it 379 ;;; into a Hemlock key event and passing it into the Hemlock command 380 ;;; interpreter. The underlying buffer becomes Hemlock's current buffer 381 ;;; and the containing pane becomes Hemlock's current window when the 382 ;;; command is processed. Use the frame's command state object. 383 384 (define-objc-method ((:void :key-down event) 385 hemlock-text-view) 386 #+debug 387 (#_NSLog #@"Key down event = %@" :address event) 388 (let* ((buffer (text-view-buffer self))) 389 (when buffer 390 (let* ((info (hemlock-frame-command-info (send self 'window)))) 391 (when info 392 (let* ((key-event (nsevent-to-key-event event))) 393 (when event 394 (unless (eq buffer hi::*current-buffer*) 395 (setf (hi::current-buffer) buffer)) 396 (let* ((pane (text-view-pane self))) 397 (unless (eql pane (hi::current-window)) 398 (setf (hi::current-window) pane))) 399 #+debug 400 (format t "~& key-event = ~s" key-event) 401 (hi::interpret-key-event key-event info)))))))) 402 403 ;;; Update the underlying buffer's point. Should really set the 404 ;;; active region (in Hemlock terms) as well. 405 (define-objc-method ((:void :set-selected-range (:<NSR>ange r) 406 :affinity (:<NSS>election<A>ffinity affinity) 407 :still-selecting (:<BOOL> still-selecting)) 408 hemlock-text-view) 409 (let* ((d (hemlock-buffer-string-cache (send self 'string))) 410 (point (hemlock::buffer-point (buffer-cache-buffer d))) 411 (location (pref r :<NSR>ange.location)) 412 (len (pref r :<NSR>ange.length))) 413 (when (eql len 0) 414 (move-hemlock-mark-to-absolute-position point d location)) 415 (send-super :set-selected-range r 416 :affinity affinity 417 :still-selecting still-selecting))) 418 419 420 421 422 ;;; Modeline-view 423 424 ;;; The modeline view is embedded in the horizontal scroll bar of the 425 ;;; scrollview which surrounds the textview in a pane. (A view embedded 426 ;;; in a scrollbar like this is sometimes called a "placard"). Whenever 427 ;;; the view's invalidated, its drawRect: method draws a string containing 428 ;;; the current values of the buffer's modeline fields. 429 430 (defclass modeline-view (ns:ns-view) 431 ((pane :foreign-type :id :accessor modeline-view-pane)) 432 (:metaclass ns:+ns-object)) 433 434 435 ;;; Attributes to use when drawing the modeline fields. There's no 436 ;;; simple way to make the "placard" taller, so using fonts larger than 437 ;;; about 12pt probably wouldn't look too good. 10pt Courier's a little 438 ;;; small, but allows us to see more of the modeline fields (like the 439 ;;; full pathname) in more cases. 440 (defloadvar *modeline-text-attributes* nil) 441 (defparameter *modeline-font-name* "Courier New Bold Italic") 442 (defparameter *modeline-font-size* 10.0) 443 444 445 ;;; Find the underlying buffer. 446 (defun buffer-for-modeline-view (mv) 447 (let* ((pane (modeline-view-pane mv))) 448 (unless (%null-ptr-p pane) 449 (let* ((tv (text-pane-text-view pane))) 450 (unless (%null-ptr-p tv) 451 (text-view-buffer tv)))))) 452 453 ;;; Draw a string in the modeline view. The font and other attributes 454 ;;; are initialized lazily; apparently, calling the Font Manager too 455 ;;; early in the loading sequence confuses some Carbon libraries that're 456 ;;; used in the event dispatch mechanism, 457 (defun draw-modeline-string (modeline-view) 458 (let* ((pane (modeline-view-pane modeline-view)) 459 (buffer (buffer-for-modeline-view modeline-view))) 460 (when buffer 461 ;; You don't want to know why this is done this way. 462 (unless *modeline-text-attributes* 463 (setq *modeline-text-attributes* 464 (create-text-attributes :color (send (@class "NSColor") 'black-color) 465 :font (default-font 466 :name *modeline-font-name* 467 :size *modeline-font-size*)))) 468 469 (let* ((string 470 (apply #'concatenate 'string 471 (mapcar 472 #'(lambda (field) 473 (funcall (hi::modeline-field-function field) 474 buffer pane)) 475 (hi::buffer-modeline-fields buffer))))) 476 (send (%make-nsstring string) 477 :draw-at-point (ns-make-point 0.0f0 0.0f0) 478 :with-attributes *modeline-text-attributes*))))) 479 480 ;;; Draw the underlying buffer's modeline string on a white background 481 ;;; with a bezeled border around it. 482 (define-objc-method ((:void :draw-rect (:<NSR>ect rect)) 483 modeline-view) 484 (declare (ignore rect)) 485 (slet ((frame (send self 'bounds))) 486 (#_NSDrawWhiteBezel frame frame) 487 (draw-modeline-string self))) 488 489 ;;; Hook things up so that the modeline is updated whenever certain buffer 490 ;;; attributes change. 491 (hi::%init-redisplay) 492 493 494 495 ;;; Modeline-scroll-view 496 497 ;;; This is just an NSScrollView that draws a "placard" view (the modeline) 498 ;;; in the horizontal scrollbar. The modeline's arbitrarily given the 499 ;;; leftmost 75% of the available real estate. 500 (defclass modeline-scroll-view (ns:ns-scroll-view) 501 ((modeline :foreign-type :id :accessor scroll-view-modeline) 502 (pane :foreign-type :id :accessor scroll-view-pane)) 503 (:metaclass ns:+ns-object)) 504 505 ;;; Making an instance of a modeline scroll view instantiates the 506 ;;; modeline view, as well. 507 508 (define-objc-method ((:id :init-with-frame (:<NSR>ect frame)) 509 modeline-scroll-view) 510 (let* ((v (send-super :init-with-frame frame))) 511 (when v 512 (let* ((modeline (make-objc-instance 'modeline-view))) 513 (send v :add-subview modeline) 514 (setf (scroll-view-modeline v) modeline))) 515 v)) 516 517 ;;; Scroll views use the "tile" method to lay out their subviews. 518 ;;; After the next-method has done so, steal some room in the horizontal 519 ;;; scroll bar and place the modeline view there. 520 521 (define-objc-method ((:void tile) modeline-scroll-view) 522 (send-super 'tile) 523 (let* ((modeline (scroll-view-modeline self))) 524 (when (and (send self 'has-horizontal-scroller) 525 (not (%null-ptr-p modeline))) 526 (let* ((hscroll (send self 'horizontal-scroller))) 527 (slet ((scrollbar-frame (send hscroll 'frame)) 528 (modeline-frame (send hscroll 'frame))) ; sic 529 (let* ((modeline-width (* (pref modeline-frame 530 :<NSR>ect.size.width) 531 0.75e0))) 532 (declare (single-float modeline-width)) 533 (setf (pref modeline-frame :<NSR>ect.size.width) 534 modeline-width 535 (the single-float 536 (pref scrollbar-frame :<NSR>ect.size.width)) 537 (- (the single-float 538 (pref scrollbar-frame :<NSR>ect.size.width)) 539 modeline-width) 540 (the single-float 541 (pref scrollbar-frame :<NSR>ect.origin.x)) 542 (+ (the single-float 543 (pref scrollbar-frame :<NSR>ect.origin.x)) 544 modeline-width)) 545 (send hscroll :set-frame scrollbar-frame) 546 (send modeline :set-frame modeline-frame))))))) 547 548 549 550 ;;; Text-pane 551 552 ;;; The text pane is just an NSBox that (a) provides a draggable border 553 ;;; around (b) encapsulates the text view and the mode line. 554 555 (defclass text-pane (ns:ns-box) 556 ((text-view :foreign-type :id :accessor text-pane-text-view) 557 (mode-line :foreign-type :id :accessor text-pane-mode-line) 558 (scroll-view :foreign-type :id :accessor text-pane-scroll-view)) 559 (:metaclass ns:+ns-object)) 560 561 ;;; Mark the pane's modeline as needing display. This is called whenever 562 ;;; "interesting" attributes of a buffer are changed. 563 564 (defun hi::invalidate-modeline (pane) 565 (send (text-pane-mode-line pane) :set-needs-display t)) 566 567 (define-objc-method ((:id :init-with-frame (:<NSR>ect frame)) 568 text-pane) 569 (let* ((pane (send-super :init-with-frame frame))) 570 (unless (%null-ptr-p pane) 571 (send pane :set-autoresizing-mask (logior 572 #$NSViewWidthSizable 573 #$NSViewHeightSizable)) 574 (send pane :set-box-type #$NSBoxPrimary) 575 (send pane :set-border-type #$NSLineBorder) 576 (send pane :set-title-position #$NSNoTitle)) 577 pane)) 578 579 580 (defun make-scrolling-text-view-for-textstorage (textstorage x y width height) 581 (slet ((contentrect (ns-make-rect x y width height))) 582 (let* ((scrollview (send (make-objc-instance 583 'modeline-scroll-view 584 :with-frame contentrect) 'autorelease))) 585 (send scrollview :set-border-type #$NSBezelBorder) 586 (send scrollview :set-has-vertical-scroller t) 587 (send scrollview :set-has-horizontal-scroller t) 588 (send scrollview :set-rulers-visible nil) 589 (send scrollview :set-autoresizing-mask (logior 590 #$NSViewWidthSizable 591 #$NSViewHeightSizable)) 592 (send (send scrollview 'content-view) :set-autoresizes-subviews t) 593 (let* ((layout (make-objc-instance 'ns-layout-manager))) 594 (send textstorage :add-layout-manager layout) 595 (send layout 'release) 596 (slet* ((contentsize (send scrollview 'content-size)) 597 (containersize (ns-make-size 598 1.0f7 599 1.0f7)) 600 (tv-frame (ns-make-rect 601 0.0f0 602 0.0f0 603 (pref contentsize :<NSS>ize.width) 604 (pref contentsize :<NSS>ize.height)))) 605 (let* ((container (send (make-objc-instance 606 'ns-text-container 607 :with-container-size containersize) 608 'autorelease))) 609 (send layout :add-text-container container) 610 (let* ((tv (send (make-objc-instance 'hemlock-text-view 611 :with-frame tv-frame 612 :text-container container) 613 'autorelease))) 614 (send tv :set-min-size (ns-make-size 615 0.0f0 616 (pref contentsize :<NSS>ize.height))) 617 (send tv :set-max-size (ns-make-size 1.0f7 1.0f7)) 618 (send tv :set-rich-text nil) 619 (send tv :set-horizontally-resizable t) 620 (send tv :set-vertically-resizable t) 621 (send tv :set-autoresizing-mask #$NSViewWidthSizable) 622 (send container :set-width-tracks-text-view nil) 623 (send container :set-height-tracks-text-view nil) 624 (send scrollview :set-document-view tv) 625 (values tv scrollview)))))))) 626 627 (defun make-scrolling-textview-for-pane (pane textstorage) 628 (slet ((contentrect (send (send pane 'content-view) 'frame))) 629 (multiple-value-bind (tv scrollview) 630 (make-scrolling-text-view-for-textstorage 631 textstorage 632 (pref contentrect :<NSR>ect.origin.x) 633 (pref contentrect :<NSR>ect.origin.y) 634 (pref contentrect :<NSR>ect.size.width) 635 (pref contentrect :<NSR>ect.size.height)) 636 (send pane :set-content-view scrollview) 637 (setf (slot-value pane 'scroll-view) scrollview 638 (slot-value pane 'text-view) tv 639 (slot-value tv 'pane) pane 640 (slot-value scrollview 'pane) pane) 641 (let* ((modeline (scroll-view-modeline scrollview))) 642 (setf (slot-value pane 'mode-line) modeline 643 (slot-value modeline 'pane) pane)) 644 tv))) 645 646 647 (defmethod hemlock-frame-command-info ((w ns:ns-window)) 648 nil) 649 650 651 (defclass hemlock-frame (ns:ns-window) 652 ((command-info :initform (hi::make-command-interpreter-info) 653 :accessor hemlock-frame-command-info)) 654 (:metaclass ns:+ns-object)) 655 656 657 (defmethod shared-initialize :after ((w hemlock-frame) 658 slot-names 659 &key &allow-other-keys) 660 (declare (ignore slot-names)) 661 (let ((info (hemlock-frame-command-info w))) 662 (when info 663 (setf (hi::command-interpreter-info-frame info) w)))) 664 665 666 (defun get-cocoa-window-flag (w flagname) 667 (case flagname 668 (:accepts-mouse-moved-events 669 (send w 'accepts-mouse-moved-events)) 670 (:cursor-rects-enabled 671 (send w 'are-cursor-rects-enabled)) 672 (:auto-display 673 (send w 'is-autodisplay)))) 674 675 676 677 (defun (setf get-cocoa-window-flag) (value w flagname) 678 (case flagname 679 (:accepts-mouse-moved-events 680 (send w :set-accepts-mouse-moved-events value)) 681 (:auto-display 682 (send w :set-autodisplay value)))) 683 684 685 686 (defun activate-window (w) 687 ;; Make w the "key" and frontmost window. Make it visible, if need be. 688 (send w :make-key-and-order-front nil)) 689 690 (defun new-hemlock-document-window (&key 691 (x 200.0) 692 (y 200.0) 693 (height 200.0) 694 (width 500.0) 695 (closable t) 696 (iconifyable t) 697 (metal t) 698 (expandable t) 699 (backing :buffered) 700 (defer nil) 701 (accepts-mouse-moved-events nil) 702 (auto-display t) 703 (activate t)) 704 (rlet ((frame :<NSR>ect :origin.x (float x) :origin.y (float y) :size.width (float width) :size.height (float height))) 705 (let* ((stylemask 706 (logior #$NSTitledWindowMask 707 (if closable #$NSClosableWindowMask 0) 708 (if iconifyable #$NSMiniaturizableWindowMask 0) 709 (if expandable #$NSResizableWindowMask 0) 710 (if metal #$NSTexturedBackgroundWindowMask 0))) 711 (backing-type 712 (ecase backing 713 ((t :retained) #$NSBackingStoreRetained) 714 ((nil :nonretained) #$NSBackingStoreNonretained) 715 (:buffered #$NSBackingStoreBuffered))) 716 (w (make-instance 717 'hemlock-frame 718 :with-content-rect frame 719 :style-mask stylemask 720 :backing backing-type 721 :defer defer))) 722 (setf (get-cocoa-window-flag w :accepts-mouse-moved-events) 723 accepts-mouse-moved-events 724 (get-cocoa-window-flag w :auto-display) 725 auto-display) 726 (when activate (activate-window w)) 727 (values w (add-pane-to-window w :reserve-below 20.0))))) 728 729 730 731 (defun add-pane-to-window (w &key (reserve-above 0.0f0) (reserve-below 0.0f0)) 732 (let* ((window-content-view (send w 'content-view))) 733 (slet ((window-frame (send window-content-view 'frame))) 734 (slet ((pane-rect (ns-make-rect 0.0f0 735 reserve-below 736 (pref window-frame :<NSR>ect.size.width) 737 (- (pref window-frame :<NSR>ect.size.height) (+ reserve-above reserve-below))))) 738 (let* ((pane (make-objc-instance 'text-pane :with-frame pane-rect))) 739 (send window-content-view :add-subview pane) 740 pane))))) 741 742 743 744 745 746 (defun textpane-for-textstorage (ts) 747 (let* ((pane (nth-value 748 1 749 (new-hemlock-document-window :activate nil))) 750 (tv (make-scrolling-textview-for-pane pane ts))) 751 (multiple-value-bind (height width) 752 (size-of-char-in-font (default-font)) 753 (size-textview-containers tv height width 24 80)) 754 pane)) 755 756 757 (defun read-file-to-hemlock-buffer (path) 758 (hemlock::find-file-buffer path)) 759 760 (defun hemlock-buffer-from-nsstring (nsstring name &rest modes) 761 (let* ((buffer (make-hemlock-buffer name :modes modes))) 762 (nsstring-to-buffer nsstring buffer))) 763 764 (defun nsstring-to-buffer (nsstring buffer) 765 (let* ((document (hi::buffer-document buffer))) 766 (setf (hi::buffer-document buffer) nil) 767 (unwind-protect 768 (progn 769 (hi::delete-region (hi::buffer-region buffer)) 770 (hi::modifying-buffer buffer) 771 (hi::with-mark ((mark (hi::buffer-point buffer) :left-inserting)) 772 (let* ((string-len (send nsstring 'length)) 773 (line-start 0) 774 (first-line-terminator ()) 775 (first-line (hi::mark-line mark)) 776 (previous first-line) 777 (buffer (hi::line-%buffer first-line))) 778 (slet ((remaining-range (ns-make-range 0 1))) 779 (rlet ((line-end-index :unsigned) 780 (contents-end-index :unsigned)) 781 (do* ((number (+ (hi::line-number first-line) hi::line-increment) 782 (+ number hi::line-increment))) 783 ((= line-start string-len) 784 (let* ((line (hi::mark-line mark))) 785 (hi::insert-string mark (make-string 0)) 786 (setf (hi::line-next previous) line 787 (hi::line-previous line) previous)) 788 nil) 789 (setf (pref remaining-range :<NSR>ange.location) line-start) 790 (send nsstring 791 :get-line-start (%null-ptr) 792 :end line-end-index 793 :contents-end contents-end-index 794 :for-range remaining-range) 795 (let* ((contents-end (pref contents-end-index :unsigned)) 796 (line-end (pref line-end-index :unsigned)) 797 (chars (make-string (- contents-end line-start)))) 798 (do* ((i line-start (1+ i)) 799 (j 0 (1+ j))) 800 ((= i contents-end)) 801 (setf (schar chars j) (code-char (send nsstring :character-at-index i)))) 802 (unless first-line-terminator 803 (let* ((terminator (code-char 804 (send nsstring :character-at-index 805 contents-end)))) 806 (setq first-line-terminator 807 (case terminator 808 (#\return (if (= line-end (+ contents-end 2)) 809 :cp/m 810 :macos)) 811 (t :unix))))) 812 (if (eq previous first-line) 813 (progn 814 (hi::insert-string mark chars) 815 (hi::insert-character mark #\newline) 816 (setq first-line nil)) 817 (if (eq string-len contents-end) 818 (hi::insert-string mark chars) 819 (let* ((line (hi::make-line 820 :previous previous 821 :%buffer buffer 822 :chars chars 823 :number number))) 824 (setf (hi::line-next previous) line) 825 (setq previous line)))) 826 (setq line-start line-end))))) 827 (when first-line-terminator 828 (setf (hi::buffer-external-format buffer) first-line-terminator)))) 829 (setf (hi::buffer-modified buffer) nil) 830 (hi::buffer-start (hi::buffer-point buffer)) 831 buffer) 832 (setf (hi::buffer-document buffer) document)))) 833 834 (setq hi::*beep-function* #'(lambda (stream) 835 (declare (ignore stream)) 836 (#_NSBeep))) 837 838 839 ;;; This function must run in the main event thread. 840 (defun %hemlock-frame-for-textstorage (ts title activate) 841 (let* ((pane (textpane-for-textstorage ts)) 842 (w (send pane 'window))) 843 (when title (send w :set-title (%make-nsstring title))) 844 (when activate (activate-window w)) 845 w)) 846 847 (defun hemlock-frame-for-textstorage (ts title activate) 848 (process-interrupt *cocoa-event-process* 849 #'%hemlock-frame-for-textstorage 850 ts title activate)) 851 852 853 (defun for-each-textview-using-storage (textstorage f) 854 (let* ((layouts (send textstorage 'layout-managers))) 855 (unless (%null-ptr-p layouts) 856 (dotimes (i (send layouts 'count)) 857 (let* ((layout (send layouts :object-at-index i)) 858 (containers (send layout 'text-containers))) 859 (unless (%null-ptr-p containers) 860 (dotimes (j (send containers 'count)) 861 (let* ((container (send containers :object-at-index j)) 862 (tv (send container 'text-view))) 863 (funcall f tv))))))))) 864 865 866 867 (defun hi::document-begin-editing (document) 868 (send (slot-value document 'textstorage) 'begin-editing)) 869 870 (defun hi::document-end-editing (document) 871 (send (slot-value document 'textstorage) 'end-editing)) 872 873 (defun hi::document-set-point-position (document) 874 (let* ((textstorage (slot-value document 'textstorage)) 875 (string (send textstorage 'string)) 876 (buffer (buffer-cache-buffer (hemlock-buffer-string-cache string))) 877 (point (hi::buffer-point buffer)) 878 (pos (mark-absolute-position point))) 879 (for-each-textview-using-storage 880 textstorage 881 #'(lambda (tv) 882 (slet ((selection (ns-make-range pos 0))) 883 (send tv :set-selected-range selection) 884 (send tv :scroll-range-to-visible selection)))))) 885 886 887 (defun textstorage-note-insertion-at-position (textstorage pos n) 888 (send textstorage 889 :edited #$NSTextStorageEditedAttributes 890 :range (ns-make-range pos 0) 891 :change-in-length n) 892 (send textstorage 893 :edited #$NSTextStorageEditedCharacters 894 :range (ns-make-range pos n) 895 :change-in-length 0)) 896 897 (defun hi::buffer-note-insertion (buffer mark n) 898 (when (hi::bufferp buffer) 899 (let* ((document (hi::buffer-document buffer)) 900 (textstorage (if document (slot-value document 'textstorage)))) 901 (when textstorage 902 (let* ((pos (mark-absolute-position mark))) 903 (unless (eq (hi::mark-%kind mark) :right-inserting) 904 (decf pos n)) 905 #+debug 0 906 (format t "~&pos = ~d, n = ~d" pos n) 907 (let* ((display (hemlock-buffer-string-cache (send textstorage 'string)))) 908 (reset-buffer-cache display) 909 (update-line-cache-for-index display pos)) 910 (textstorage-note-insertion-at-position textstorage pos n)))))) 911 912 913 914 (defun hi::buffer-note-deletion (buffer mark n) 915 (when (hi::bufferp buffer) 916 (let* ((document (hi::buffer-document buffer)) 917 (textstorage (if document (slot-value document 'textstorage)))) 918 (when textstorage 919 (let* ((pos (mark-absolute-position mark))) 920 (setq n (abs n)) 921 (let* ((cache (hemlock-buffer-string-cache (send textstorage 'string)))) 922 (reset-buffer-cache cache) 923 (update-line-cache-for-index cache pos)) 924 925 (send textstorage 926 :edited #$NSTextStorageEditedAttributes 927 :range (ns-make-range pos n) 928 :change-in-length (- n))))))) 929 930 (defun hi::set-document-modified (document flag) 931 (send document 932 :update-change-count (if flag #$NSChangeDone #$NSChangeCleared))) 933 934 935 (defun hi::document-panes (document) 936 (let* ((ts (slot-value document 'textstorage)) 937 (panes ())) 938 (for-each-textview-using-storage 939 ts 940 #'(lambda (tv) 941 (let* ((pane (text-view-pane tv))) 942 (unless (%null-ptr-p pane) 943 (push pane panes))))) 944 panes)) 945 24 946 25 947 … … 62 984 (send window :set-resize-increments 63 985 (ns-make-size char-width char-height))))))) 64 65 (defun info-from-document (doc)66 (with-lock-grabbed (*open-editor-documents-lock*)67 (find doc *open-editor-documents* :key #'cocoa-editor-info-document)))68 69 (defun info-from-controller (controller)70 (with-lock-grabbed (*open-editor-documents-lock*)71 (find controller *open-editor-documents* :key #'cocoa-editor-info-controller)))72 73 74 75 986 76 987 … … 96 1007 (let* ((doc (send-super 'init))) 97 1008 (unless (%null-ptr-p doc) 98 (let* ((buffer ( hi::make-buffer1009 (let* ((buffer (make-hemlock-buffer 99 1010 (lisp-string-from-nsstring (send doc 'display-name)) 100 1011 :modes '("Lisp")))) 101 1012 (setf (slot-value doc 'textstorage) 102 (make-textstorage-for-hemlock-buffer 103 buffer) 1013 (make-textstorage-for-hemlock-buffer buffer) 104 1014 (hi::buffer-document buffer) doc))) 105 1015 doc)) … … 112 1022 (let* ((pathname (lisp-string-from-nsstring filename)) 113 1023 (buffer-name (hi::pathname-to-buffer-name pathname)) 114 (buffer (hi::make-buffer buffer-name)) 1024 (buffer (or 1025 (hemlock-document-buffer self) 1026 (let* ((b (make-hemlock-buffer buffer-name))) 1027 (setf (hi::buffer-pathname b) pathname) 1028 (setf (slot-value self 'textstorage) 1029 (make-textstorage-for-hemlock-buffer b)) 1030 b))) 115 1031 (data (make-objc-instance 'ns:ns-data 116 1032 :with-contents-of-file filename)) 117 1033 (string (make-objc-instance 'ns:ns-string 118 1034 :with-data data 119 :encoding #$NS MacOSRomanStringEncoding)))120 ( setf (hi::buffer-pathname buffer) pathname)1035 :encoding #$NSASCIIStringEncoding))) 1036 (hi::document-begin-editing self) 121 1037 (nsstring-to-buffer string buffer) 122 (hi::buffer-start (hi::buffer-point buffer)) 1038 (let* ((textstorage (slot-value self 'textstorage)) 1039 (display (hemlock-buffer-string-cache (send textstorage 'string)))) 1040 (reset-buffer-cache display) 1041 (update-line-cache-for-index display 0) 1042 (textstorage-note-insertion-at-position 1043 textstorage 1044 0 1045 (hemlock-buffer-length buffer))) 1046 (hi::document-end-editing self) 123 1047 (setf (hi::buffer-modified buffer) nil) 124 1048 (hi::process-file-options buffer pathname) 125 (setf (slot-value self 'textstorage) 126 (make-textstorage-for-hemlock-buffer buffer) 127 (hi::buffer-document buffer) (%setf-macptr (%null-ptr) self)))) 1049 self)) 128 1050 129 1051 1052 (defmethod hemlock-document-buffer (document) 1053 (let* ((string (send (slot-value document 'textstorage) 'string))) 1054 (unless (%null-ptr-p string) 1055 (let* ((cache (hemlock-buffer-string-cache string))) 1056 (when cache (buffer-cache-buffer cache)))))) 130 1057 131 1058 (define-objc-method ((:id :data-representation-of-type type) 132 1059 lisp-editor-document) 133 1060 (declare (ignorable type)) 134 (send (send (slot-value self 'text-view) 'string) 1061 (let* ((buffer (hemlock-document-buffer self))) 1062 (when buffer 1063 (setf (hi::buffer-modified buffer) nil))) 1064 (send (send (slot-value self 'textstorage) 'string) 135 1065 :data-using-encoding #$NSASCIIStringEncoding 136 1066 :allow-lossy-conversion t)) 137 1067 1068 1069 ;;; Shadow the setFileName: method, so that we can keep the buffer 1070 ;;; name and pathname in synch with the document. 1071 (define-objc-method ((:void :set-file-name full-path) 1072 lisp-editor-document) 1073 (send-super :set-file-name full-path) 1074 (let* ((buffer (hemlock-document-buffer self))) 1075 (when buffer 1076 (let* ((new-pathname (lisp-string-from-nsstring full-path))) 1077 (setf (hi::buffer-name buffer) (hi::pathname-to-buffer-name new-pathname)) 1078 (setf (hi::buffer-pathname buffer) new-pathname))))) 1079 138 1080 (define-objc-method ((:void make-window-controllers) lisp-editor-document) 139 1081 (let* ((controller (make-objc-instance … … 193 1135 (setf (slot-value self 'textstorage) (%null-ptr)) 194 1136 (unless (%null-ptr-p textstorage) 195 (close-hemlock-textstorage textstorage))) 196 (let* ((info (info-from-document self))) 197 (when info 198 (let* ((proc (cocoa-editor-info-listener info))) 199 (when proc 200 (setf (cocoa-editor-info-listener info) nil) 201 (process-kill proc))) 202 (with-lock-grabbed (*open-editor-documents-lock*) 203 (setq *open-editor-documents* 204 (delete info *open-editor-documents*)))))) 1137 (close-hemlock-textstorage textstorage)))) 205 1138 206 1139 -
trunk/ccl/examples/cocoa-listener.lisp
r569 r592 22 22 (type list *open-editor-documents*)) 23 23 24 #-hemlock 25 (progn 24 25 (defloadvar *cocoa-listener-count* 0) 26 27 26 28 (defun new-listener-process (procname input-fd output-fd) 27 29 (make-mcl-listener-process … … 38 40 #$_PC_MAX_INPUT)) 39 41 #'(lambda () 40 (let* (( info (with-lock-grabbed (*open-editor-documents-lock*)41 (find *current-process* *open-editor-documents*42 :key #'cocoa-editor-info-listener))))43 (when info44 (setf ( cocoa-editor-info-listener info) nil)45 (send (cocoa-editor-info-document info)42 (let* ((buf (find *current-process* hi:*buffer-list* 43 :key #'hi::buffer-process)) 44 (doc (if buf (hi::buffer-document buf)))) 45 (when doc 46 (setf (hi::buffer-process buf) nil) 47 (send doc 46 48 :perform-selector-on-main-thread (@selector "close") 47 49 :with-object (%null-ptr) … … 62 64 ((filehandle :foreign-type :id) ;Filehandle for I/O 63 65 (clientfd :foreign-type :int) ;Client (listener)'s side of pty 64 (outpos :foreign-type :unsigned) ;Position in textview buffer65 (userta :foreign-type :id) ;Typing attributes for user input66 (systa :foreign-type :id) ;Typing attributes for system output67 (usercolor :foreign-type :id) ;Text foreground color for user input68 66 ) 69 67 (:metaclass ns:+ns-object) 70 68 ) 71 69 72 (define-objc-method ((:void window-did-load) lisp-listener-window-controller) 73 (multiple-value-bind (server client) (ignore-errors (open-pty-pair)) 74 (when server 75 (let* ((fh (make-objc-instance 76 'ns-file-handle 77 :with-file-descriptor (setup-server-pty server) 78 :close-on-dealloc t))) 79 (setf (slot-value self 'filehandle) fh) 80 (setf (slot-value self 'clientfd) (setup-client-pty client)) 81 (send (send (@class ns-notification-center) 'default-center) 82 :add-observer self 83 :selector (@selector "gotData:") 84 :name *NSFileHandleReadCompletionNotification* 85 :object fh) 86 (send fh 'read-in-background-and-notify))))) 70 (define-objc-method ((:id :init-with-window w) 71 lisp-listener-window-controller) 72 (let* ((self (send-super :init-with-window w))) 73 (unless (%null-ptr-p self) 74 (multiple-value-bind (server client) (ignore-errors (open-pty-pair)) 75 (when server 76 (let* ((fh (make-objc-instance 77 'ns-file-handle 78 :with-file-descriptor (setup-server-pty server) 79 :close-on-dealloc t))) 80 (setf (slot-value self 'filehandle) fh) 81 (setf (slot-value self 'clientfd) (setup-client-pty client)) 82 (send (send (@class ns-notification-center) 'default-center) 83 :add-observer self 84 :selector (@selector "gotData:") 85 :name *NSFileHandleReadCompletionNotification* 86 :object fh) 87 (send fh 'read-in-background-and-notify))))) 88 self)) 87 89 88 90 (define-objc-method ((:void :got-data notification) 89 91 lisp-listener-window-controller) 90 (with-slots (filehandle systa outpos textview) self92 (with-slots (filehandle) self 91 93 (let* ((data (send (send notification 'user-info) 92 94 :object-for-key *NSFileHandleNotificationDataItem*)) 93 (tv textview) 95 (document (send self 'document)) 96 (data-length (send data 'length)) 97 (buffer (hemlock-document-buffer document)) 98 (string (make-string data-length)) 94 99 (fh filehandle)) 95 (unless (%null-ptr-p tv) 96 (let* ((buffer-text (send tv 'text-storage)) 97 (s (make-objc-instance 'ns-string 98 :with-data data 99 :encoding #$NSASCIIStringEncoding)) 100 (str (make-objc-instance 'ns-attributed-string 101 :with-string s 102 :attributes systa))) 103 (send buffer-text :append-attributed-string str) 104 105 (let* ((textlen (send buffer-text 'length))) 106 (send tv :scroll-range-to-visible (ns-make-range textlen 0)) 107 (setq outpos textlen)) 108 (send str 'release))) 109 (send self 'update-package-name) 100 (declare (dynamic-extent string)) 101 (%copy-ptr-to-ivector (send data 'bytes) 0 string 0 data-length) 102 (hi::insert-string (hi::buffer-point buffer) string) 110 103 (send fh 'read-in-background-and-notify)))) 111 104 112 (define-objc-method ((:void update-package-name) 113 lisp-listener-window-controller) 114 (let* ((info (info-from-controller self)) 115 (proc (if info (cocoa-editor-info-listener info))) 116 (package (if proc (ignore-errors (symbol-value-in-process 117 '*package* 118 proc)))) 119 (name (if (typep package 'package) 120 (shortest-package-name package) 121 ""))) 122 (with-cstrs ((name name)) 123 (send self :display-package-name (send (@class ns-string) 124 :string-with-c-string name))))) 125 126 127 105 #| 128 106 ;;; The Lisp-Listener-Window-Controller is the textview's "delegate": it 129 107 ;;; gets consulted before certain actions are performed, and can … … 142 120 (send tv :set-typing-attributes (slot-value self 'userta)) 143 121 t))) 144 122 |# 145 123 146 124 ;;; Action methods implemented by the controller (in its role as the … … 155 133 :allow-lossy-conversion t))) 156 134 157 (define-objc-method ((:void :insert-newline tv) 158 lisp-listener-window-controller) 159 (with-slots (outpos usercolor) self 160 (let* ((textbuf (send tv 'text-storage)) 161 (textlen (send textbuf 'length)) 162 (textstring (send tv 'string))) 163 (slet ((r (send tv 'selected-range))) 164 (let* ((curpos (pref r :<NSR>ange.location)) 165 (curlen (pref r :<NSR>ange.length))) 166 (cond ((>= curpos outpos) 167 ;; Insert the newline at the end of any selection. 168 (incf curpos (pref r :<NSR>ange.length)) 169 (send tv :set-selected-range (ns-make-range curpos 0)) 170 (send tv :insert-newline self) 171 (incf curpos) 172 (incf textlen) 173 (when (= curpos textlen) 174 (let* ((sendlen (- textlen outpos)) 175 (sendstring 176 (send textstring 177 :substring-with-range (ns-make-range outpos sendlen)))) 178 (setf (pref r :<NSR>ange.location) 0 179 (pref r :<NSR>ange.length) sendlen) 180 (multiple-value-bind (ok second-value) 181 (balanced-expressions-in-range-forward r sendstring) 182 (if ok 183 (if second-value 184 (progn 185 (send self :send-string sendstring) 186 (setq outPos textlen))) 187 (if second-value 188 (#_NSBeep))))))) 189 ;; If there's a selection, copy it to the end of the 190 ;; buffer, then move to the end of the buffer. 191 ((> curlen 0) 192 (slet ((endrange (ns-make-range textlen 0))) 193 (send tv :set-selected-range endrange) 194 (send tv :insert-text 195 (send textstring :substring-with-range r)) 196 (setf (pref endrange :<NSR>ange.location) 197 (send textbuf 'length)) 198 (send tv :scroll-range-to-visible endrange))) 199 ;; No selection, insertion point is before outpos (in 200 ;; history or in output. If in history, copy history 201 ;; item to end of buffer, otherwise, do nothing. 202 (t 203 (rlet ((lr :<NSR>ange) 204 (fullrange :<NSR>ange :location 0 :length textlen)) 205 (let* ((attr 206 (send textbuf 207 :attribute #@"NSColor" 208 :at-index curpos 209 :longest-effective-range lr 210 :in-range fullrange))) 211 (when (send attr :is-equal usercolor) 212 (let* ((history-start (pref lr :<NSR>ange.location)) 213 (history-len (pref lr :<NSR>ange.length))) 214 (when (eql 215 (send textstring 216 :character-at-index 217 (+ history-start (1- history-len))) 218 (char-code #\NewLine)) 219 (decf (pref lr :<NSR>ange.length))) 220 (unless (eql 0 history-len) 221 (setf (pref fullrange :<NSR>ange.location) 222 textlen 223 (pref fullrange :<NSR>ange.length) 224 0) 225 (send tv :set-selected-range fullrange) 226 (send tv :insert-text 227 (send textstring :substring-with-range lr)) 228 (setf (pref fullrange :<NSR>ange.location) 229 (send textbuf 'length)) 230 (send tv :scroll-range-to-visible fullrange))))))))))))) 231 232 ;;; Force a break in the listener process. 233 (define-objc-method ((:id :interrupt tv) lisp-listener-window-controller) 234 (declare (ignore tv)) 235 (let* ((info (info-from-controller self)) 236 (proc (if info (cocoa-editor-info-listener info)))) 237 (when proc (force-break-in-listener proc)) 238 self)) 239 240 ;;; This exists solely for debugging. 241 (define-objc-method ((:id :log-attrs tv) lisp-listener-window-controller) 242 (slet ((selection (send tv 'selected-range))) 243 (rlet ((lr :<NSR>ange)) 244 (let* ((textbuf (send tv 'text-storage)) 245 (attr 246 (send textbuf 247 :attributes-at-index (pref selection :<NSR>ange.location) 248 :longest-effective-range lr 249 :in-range (ns-make-range 0 (send textbuf 'length))))) 250 (#_NSLog #@"Attr = %@, range = [%d,%d]" 251 :address attr 252 :unsigned-fullword (pref lr :<NSR>ange.location) 253 :unsigned-fullword (pref lr :<NSR>ange.length))) 254 self))) 255 256 ;;; If we're at the end of the buffer and at the start of a line (either 257 ;;; at outpos or after a newline), send an EOF (0 bytes of data) to the 258 ;;; listener. Otherwise, have the textview do a "deleteForward:" 135 136 137 259 138 (define-objc-method ((:id :delete-forward tv) lisp-listener-window-controller) 260 139 (with-slots (outpos filehandle) self … … 279 158 self)))) 280 159 281 (define-objc-method ((:id :add-modeline tv) lisp-listener-window-controller) 282 (declare (ignore tv)) 283 self 284 ) 285 286 (define-objc-method ((:id :reparse-modeline tv) 287 lisp-listener-window-controller) 288 (declare (ignore tv)) 289 self 290 ) 160 161 162 291 163 292 164 (define-objc-method ((:void dealloc) lisp-listener-window-controller) … … 297 169 298 170 299 )300 171 ;;; The LispListenerDocument class. 301 172 … … 305 176 (:metaclass ns:+ns-object)) 306 177 307 #-hemlock 308 (progn 178 309 179 (define-objc-class-method ((:id top-listener) lisp-listener-document) 310 180 (let* ((all-documents (send *NSApp* 'ordered-Documents))) … … 316 186 (defun symbol-value-in-top-listener-process (symbol) 317 187 (let* ((listenerdoc (send (@class lisp-listener-document) 'top-listener)) 318 (info (info-from-document listenerdoc)) 319 (process (if info (cocoa-editor-info-listener info)))) 188 (buffer (unless (%null-ptr-p listenerdoc) 189 (hemlock-document-buffer listenerdoc))) 190 (process (if buffer (hi::buffer-process buffer)))) 320 191 (if process 321 192 (ignore-errors (symbol-value-in-process symbol process)) … … 327 198 nil) 328 199 329 (define-objc-method ((:void make-window-controllers) lisp-listener-document) 330 (let* ((controller (make-objc-instance 331 'lisp-listener-window-controller 332 :with-window-nib-name (send self 'window-nib-name) 333 :owner self))) 334 (send self :add-window-controller controller) 335 (send controller 'release))) 336 337 338 339 340 341 (defloadvar *cocoa-listener-count* 17) 342 343 (define-objc-method ((:void :window-controller-did-load-nib acontroller) 200 201 (define-objc-method ((:id init) 344 202 lisp-listener-document) 345 (send-super :window-controller-did-load-nib acontroller) 346 ;; We'll use attribute-change information to distinguish user 347 ;; input from system output. Be fascist about letting the 348 ;; user change anything. 349 (with-slots ((textview text-view) packagename echoarea filedata) self 350 (send textview :set-rich-text nil) 351 (send textview :set-uses-font-panel nil) 352 (let* ((listener-name (if (eql 1 (incf *cocoa-listener-count*)) 203 (let* ((doc (send-super 'init))) 204 (unless (%null-ptr-p doc) 205 (let* ((listener-name (if (eql 1 (incf *cocoa-listener-count*)) 353 206 "Listener" 354 207 (format nil 355 208 "Listener-~d" *cocoa-listener-count*))) 356 (info (info-from-document self))) 357 (setf (cocoa-editor-info-listener info) 358 (let* ((tty (slot-value acontroller 'clientfd))) 359 (new-listener-process listener-name tty tty))) 360 (send self :set-file-name (%make-nsstring listener-name))) 361 (setf (slot-value acontroller 'textview) textview 362 (slot-value acontroller 'echoarea) echoarea 363 (slot-value acontroller 'packagename) packagename) 364 (let* ((userta (send (send textview 'typing-attributes) 'retain)) 365 (systa (create-text-attributes :color (send (@class ns-color) 366 'blue-color)))) 367 (setf (slot-value acontroller 'userta) 368 userta 369 (slot-value acontroller 'usercolor) 370 (send userta :value-for-key #@"NSColor") 371 (slot-value acontroller 'systa) 372 systa)) 373 (send textview :set-delegate acontroller) 374 (unless (%null-ptr-p filedata) 375 (send textview 376 :replace-characters-in-range (ns-make-range 0 0) 377 :with-rtfd filedata)))) 209 (buffer (hemlock-document-buffer doc))) 210 (send doc :set-file-name (%make-nsstring listener-name)) 211 (setf (hi::buffer-pathname buffer) nil 212 (hi::buffer-minor-mode buffer "Listener") t 213 (hi::buffer-name buffer) listener-name))) 214 doc)) 215 216 (define-objc-method ((:void make-window-controllers) lisp-listener-document) 217 (let* ((controller (make-objc-instance 218 'lisp-listener-window-controller 219 :with-window (%hemlock-frame-for-textstorage 220 (slot-value self 'textstorage) nil nil))) 221 (listener-name (hi::buffer-name (hemlock-document-buffer self)))) 222 (send self :add-window-controller controller) 223 (send controller 'release) 224 (setf (hi::buffer-process (hemlock-document-buffer self)) 225 (let* ((tty (slot-value controller 'clientfd))) 226 (new-listener-process listener-name tty tty))) 227 controller)) 378 228 379 229 ;;; This is almost completely wrong: we need to ensure that the form 380 230 ;;; is read in the correct package, etc. 231 #| 381 232 (defun send-to-top-listener (sender-info nsstring &optional (append-newline t)) 382 233 (declare (ignorable sender-info)) … … 391 242 " 392 243 )))))) 393 394 395 ); #-hemlock 244 |# 245 246 247
Note:
See TracChangeset
for help on using the changeset viewer.
