source: release/1.11/source/cocoa-ide/hemlock/src/display.lisp

Last change on this file was 16688, checked in by R. Matthew Emerson, 9 years ago

Merge copyright/license header changes to 1.11 release branch.

File size: 20.0 KB
RevLine 
[15906]1;;;
[16688]2;;; Copyright 2013 Clozure Associates
[15906]3;;;
[16688]4;;; Licensed under the Apache License, Version 2.0 (the "License");
5;;; you may not use this file except in compliance with the License.
6;;; You may obtain a copy of the License at
7;;;
8;;; http://www.apache.org/licenses/LICENSE-2.0
9;;;
10;;; Unless required by applicable law or agreed to in writing, software
11;;; distributed under the License is distributed on an "AS IS" BASIS,
12;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13;;; See the License for the specific language governing permissions and
14;;; limitations under the License.
[15906]15
16(in-package :hemlock)
17
18;; Functions used by the IDE display code.
19
[16343]20(defmacro with-display-context ((view &optional pane) &body body)
21 (let ((viewvar (gensym "VIEW")))
22 `(let* ((,viewvar ,view)
23 (hi::*current-view* ,viewvar)
24 (hi::*current-buffer* (ecase ,pane
25 ((:text) (hi::hemlock-view-buffer ,viewvar))
26 ((:echo) (hi::hemlock-echo-area-buffer ,viewvar))
27 ((nil) (hi::hemlock-view-current-buffer ,viewvar)))))
28 (hi::with-default-view-for-buffer (,viewvar)
29 ,@body))))
[15906]30
31;; User variable. Maps symbol categories (see compute-symbol-category) to color specs
32(defvar *lisp-code-colors* '((:string :blue)
33 (:comment :brown)
34 (:double-comment :orange)
35 (:triple-comment :red)
36 (:system-symbol (0 .5 0 1))
37 (:definition (1 0 1 1))
[15931]38 (:keyword :purple)
[15966]39 (:unmatched-paren :red)
40 (:matched-paren (:background (0.3 0.875 0.8125 1)))))
41
[15930]42;; Cache for actual color objects.
[15966]43(ccl:defloadvar *lisp-code-colors-cache* nil)
[15906]44
[15930]45;; (cached-lisp-code-colors)
46(defun cached-lisp-code-colors ()
47 (let ((specs (car *lisp-code-colors-cache*))
48 (alist (cdr *lisp-code-colors-cache*))
49 (user-alist *lisp-code-colors*))
50 (flet ((get-spec (cell)
51 (let ((spec (cdr cell)))
52 (if (and (consp spec) (null (cdr spec)))
53 (car spec)
54 spec))))
55 (declare (inline get-spec))
56 (unless (and (eql (length user-alist) (length alist))
57 (loop for spec in specs for cell in alist for user-cell in user-alist
58 always (and (eq (car cell) (car user-cell)) (eq spec (get-spec user-cell)))))
59 (setq specs (mapcar #'get-spec user-alist))
60 (setq alist (mapcar #'(lambda (user-cell spec)
[15966]61 (cons (car user-cell)
62 (if (and (consp spec) (keywordp (car spec)) (null (cddr spec)))
63 (cons (car spec) (hemlock-ext:lookup-color (cadr spec)))
64 (cons :foreground (hemlock-ext:lookup-color spec)))))
[15930]65 user-alist specs))
66 (setq *lisp-code-colors-cache* (cons specs alist)))
67 alist)))
[15906]68
[15966]69(defun hemlock:compute-paren-highlighting ()
70 "Compute the positions of the characters to be shown as matching parens"
71 (let* ((point (current-point))
72 (color-alist (cached-lisp-code-colors))
73 (color (cdr (assq :matched-paren color-alist))))
74 (when color
75 (cond ((test-char (next-character point) :lisp-syntax :open-paren)
76 (pre-command-parse-check point)
77 (when (valid-spot point t)
78 (with-mark ((temp point))
79 (when (list-offset temp 1)
80 (list (cons (mark-absolute-position point) color)
81 (cons (1- (mark-absolute-position temp)) color))))))
82 ((test-char (previous-character point) :lisp-syntax :close-paren)
83 (pre-command-parse-check point)
84 (when (valid-spot point nil)
[15906]85 (with-mark ((temp point))
86 (when (list-offset temp -1)
[15966]87 (list (cons (mark-absolute-position temp) color)
88 (cons (1- (mark-absolute-position point)) color))))))))))
[15906]89
90
91;; Return nil to use the default Cocoa selection, which will be word for double-click, line for triple.
92(defun hemlock:selection-for-click (mark paragraph-mode-p)
93 ;; Handle lisp mode specially, otherwise just go with default Cocoa behavior
[16446]94 (when (lisp-mode? mark)
[15906]95 (unless paragraph-mode-p
96 (let ((region (word-region-at-mark mark)))
97 (when region
98 (return-from selection-for-click region))))
99 (pre-command-parse-check mark)
100 (form-region-at-mark mark)))
101
102(defun hemlock:move-point-for-click (buffer index)
103 (let* ((point (buffer-point buffer))
104 (mark (and (%buffer-region-active-p buffer) (buffer-mark buffer))))
105 (setf (hi::buffer-region-active buffer) nil)
106 (unless (eql (mark-absolute-position point) index) ;; if point is already at target, leave mark alone
107 (if (and mark (eql (mark-absolute-position mark) index))
108 (move-mark mark point)
109 (push-new-buffer-mark point))
110 (move-to-absolute-position point index))))
111
112
113(defun shortest-package-name (package)
114 (let* ((name (package-name package))
115 (len (length name)))
116 (dolist (nick (package-nicknames package) name)
117 (let* ((nicklen (length nick)))
118 (if (< nicklen len)
119 (setq name nick len nicklen))))))
120
[15926]121(defun hemlock:update-current-package (&optional pkg (buffer (current-buffer)))
[16446]122 (when (lisp-mode? buffer)
[15906]123 (unless pkg
124 (setq pkg (or (package-at-mark (current-point))
[15926]125 (variable-value 'default-package :buffer buffer))))
[15906]126 (when pkg
127 (let* ((name (if (packagep pkg) (package-name pkg) (string pkg)))
[15926]128 (curname (variable-value 'current-package :buffer buffer)))
[15906]129 (when (setq pkg (find-package name))
130 (setq name (shortest-package-name pkg)))
131 (if (or (null curname)
132 (not (string= curname name)))
[15926]133 (setf (variable-value 'current-package :buffer buffer) name))))))
[15906]134
135;; advance to next symbol, ignoring form boundaries, strings, etc.
136(defun %scan-to-symbol (mark)
137 (loop while (%scan-to-form mark t)
138 do (unless (test-char (next-character mark) :lisp-syntax (or :string-quote :open-paren :close-paren))
139 (return mark))
140 do (mark-after mark)))
141
142;; Advance to next atom, ignoring open parens (but not close parens, unlike above).
143(defun %scan-down-to-atom (mark)
144 (loop while (%scan-to-form mark t)
145 do (unless (test-char (next-character mark) :lisp-syntax :open-paren)
146 (return mark))
147 do (mark-after mark)))
148
149#+debug
150(defun buffer-short-name ()
151 (let* ((full-name (buffer-name (current-buffer)))
152 (pos (position #\space full-name)))
153 (if pos (subseq full-name 0 pos) full-name)))
154
155;; When get a cache miss, means we'll fill in parsing and line-origin caches for the whole buffer, so might
156;; as well get a little extra coloring pre-computed in as well, for smoother scrolling...
157(defparameter $coloring-cache-extra 1000)
158
159(defstruct coloring-cache
160 (tick nil)
161 (start 0)
162 (end 0)
[15930]163 (colors nil)
[15906]164 (data nil))
165
166(defun make-sym-vec ()
167 (make-array 0 :displaced-to "" :adjustable t))
168
169(defun displace-to-region (sym-vec start-mark end-mark)
170 (let* ((sym-line (mark-line start-mark))
171 (line-str (line-string sym-line))
172 (start-pos (mark-charpos start-mark))
173 (end-pos (if (eq sym-line (mark-line end-mark))
174 (mark-charpos end-mark)
175 (progn
176 (setq line-str (region-to-string (region start-mark end-mark)))
177 (setq start-pos 0)
178 (length line-str)))))
179 (ccl::%displace-array sym-vec nil (- end-pos start-pos) line-str start-pos T)))
180
181#+debug
182(defmethod print-object ((cache coloring-cache) stream)
183 (print-unreadable-object (stream cache :identity nil :type t)
184 (format stream "~s:~s @~s" (coloring-cache-start cache) (coloring-cache-end cache) (coloring-cache-tick cache))))
185
186(defun hemlock:compute-syntax-coloring (start-pos length)
187 (let* ((buffer (current-buffer))
188 (end-pos (+ start-pos length))
189 (tick (buffer-signature buffer))
[15930]190 (colors (cached-lisp-code-colors))
[15906]191 (cache (or (getf (buffer-plist buffer) 'coloring-cache)
192 (setf (getf (buffer-plist buffer) 'coloring-cache) (make-coloring-cache)))))
193 (unless (and (eql (coloring-cache-tick cache) tick)
194 (<= (coloring-cache-start cache) start-pos)
195 (<= end-pos (coloring-cache-end cache))
[15930]196 (eq colors (coloring-cache-colors cache)))
[15906]197 (setq start-pos (max 0 (- start-pos $coloring-cache-extra)))
198 (setq end-pos (+ end-pos $coloring-cache-extra))
199 (let ((res (compute-syntax-coloring-in-region buffer start-pos end-pos)))
200 (setf (coloring-cache-start cache) start-pos
201 (coloring-cache-end cache) end-pos
[15930]202 (coloring-cache-colors cache) colors
[15906]203 (coloring-cache-data cache) res
204 (coloring-cache-tick cache) tick)))
205 (coloring-cache-data cache)))
206
207(defun case-insensitive-string-to-symbol (string pkg)
208 (when (null pkg) (setq pkg *package*))
[16485]209 (let* ((str (coerce string 'simple-string)))
210 (ignore-errors
211 (case (readtable-case *readtable*)
212 (:upcase (find-symbol (nstring-upcase str) pkg))
213 (:downcase (find-symbol (nstring-downcase str) pkg))
214 (:preserve (find-symbol str pkg))
215 (t (find-symbol (nstring-upcase str) pkg)
216 ; not the right way to handle :invert. Do we care?
217 )))))
[15906]218
219;; Try to exclude use of symbol in data.
220(defun mark-at-invocation-p (start-mark)
221 (and (test-char (previous-character start-mark) :lisp-syntax :open-paren)
222 (prog2
223 (mark-before start-mark)
[15930]224 (not (test-char (previous-character start-mark) :lisp-syntax (or :prefix :open-paren)))
[15906]225 (mark-after start-mark))))
226
227(defun compute-symbol-category (start-mark sym)
228 (when (ccl::non-nil-symbol-p sym)
229 (cond ((and (or (macro-function sym)
230 (ccl::special-form-p sym))
231 (mark-at-invocation-p start-mark))
232 :system-symbol)
233 ((keywordp sym)
234 :keyword)
235 (t nil))))
236
237(defvar *defining-symbols*
238 '(defun defgeneric defmethod defmacro
239 define-compiler-macro define-modify-macro define-symbol-macro
240 define-setf-expander defsetf
241 defvar defparameter defconstant
242 define-method-combination
243 defclass defstruct deftype define-condition
244 defpackage
245 ccl:advise
246 ccl:def-load-pointers
247 ccl:define-definition-type
248 ccl:defloadvar
249 ccl:defglobal ccl:defstaticvar ccl:define-declaration ccl:defstatic ccl:defcallback ccl:define-setf-method
250 ccl:define-character-encoding
251 ccl:defglobal
252 hemlock-interface:defcommand
253 hemlock-interface:define-file-option
254 hemlock-interface:define-file-type-hook
255 hemlock-interface:define-keysym-code
256 gui::def-cocoa-default
257 objc:define-objc-class-method
258 objc:define-objc-method
259 objc:defmethod))
260
261;; If true, the next atom following this sym will be automatically categorized as :definition, without going through compute-symbol-category.
262(defun defining-symbol-p (start-mark sym)
263 (and (mark-at-invocation-p start-mark)
264 (or (member sym *defining-symbols*) ;; recognize these even if indented or embedded.
265 (and (eql (mark-charpos start-mark) 1) ;; but accept any toplevel "(def".
266 (or (let ((str (string sym)))
267 (and (> (length str) 3) (string-equal "def" str :end2 3)))
268 ;; color top-level setq's, just for fun
269 (eq sym 'setq))))))
270
271(defun compute-string/comment-coloring-in-region (region-start region-end)
[16446]272 (when (lisp-mode? region-start)
273 (let* ((lisp-code-colors (cached-lisp-code-colors))
274 (start-line (mark-line region-start))
275 (end-line (line-next (mark-line region-end)))
276 (start-charpos (mark-charpos region-start)))
277 (assert (not (eq start-line end-line)))
278 (loop
279 for line = start-line then (line-next line) until (eq line end-line)
280 for info = (getf (line-plist line) 'lisp-info)
281 when info
282 nconc (loop with origin = (hi::line-origin line)
283 for last-end = 0 then end-offset
284 for (start-offset . end-offset) in (lisp-info-ranges-to-ignore info)
285 for syntax = (if (eql start-offset 0)
286 (lisp-info-begins-quoted info)
287 (if (< last-end start-offset)
288 (character-attribute :lisp-syntax (line-character line (1- start-offset)))
289 :comment))
290 do (when (member syntax '(:symbol-quote :string-quote))
291 (when (< 0 start-offset)
292 (decf start-offset))
293 (when (< end-offset (line-length line))
294 (incf end-offset)))
295 unless (and (eq line start-line) (<= end-offset start-charpos))
296 nconc (let* ((type (case syntax
297 ((:char-quote :symbol-quote) nil)
298 (:string-quote :string)
299 (t (loop for i from start-offset as nsemi upfrom 0
300 until (or (eql nsemi 3)
301 (eql i end-offset)
302 (not (test-char (line-character line i) :lisp-syntax :comment)))
303 finally (return (case nsemi
304 (2 :double-comment)
305 (3 :triple-comment)
306 (t :comment)))))))
307 (color (and type (cdr (assq type lisp-code-colors)))))
308 (when color
309 (list (list* (+ origin start-offset) (- end-offset start-offset) color)))))))))
[15906]310
311(defun coloring-region (start-mark end-mark color)
312 (when color
313 (let* ((start (mark-absolute-position start-mark))
314 (end (mark-absolute-position end-mark))
315 (len (- end start)))
316 (when (> len 0)
317 (list* start len color)))))
318
319(defun compute-symbol-coloring-in-region (region-start region-end)
[16446]320 (when (lisp-mode? region-start)
321 (let* ((sym-vec (make-sym-vec))
322 (pkg nil)
323 (lisp-colors (cached-lisp-code-colors))
324 (defn-color (cdr (assq :definition lisp-colors))))
325 (with-mark ((start-mark region-start)
326 (end-mark region-start))
327 (let ((pkgname (package-at-mark region-end end-mark)))
328 (when pkgname
329 (when (mark< region-start end-mark)
330 ;; Argh, more than one package in region. KLUDGE!!
331 (return-from compute-symbol-coloring-in-region
332 (nconc (compute-symbol-coloring-in-region region-start (mark-before end-mark))
333 (compute-symbol-coloring-in-region (mark-after end-mark) region-end))))
334 (setq pkg (find-package pkgname))))
335 (loop
336 while (and (%scan-to-symbol start-mark) (mark< start-mark region-end))
337 for sym = (progn
338 (move-mark end-mark start-mark)
339 (unless (forward-form end-mark) (move-mark end-mark region-end))
340 (case-insensitive-string-to-symbol (displace-to-region sym-vec start-mark end-mark) pkg))
341 for type = (compute-symbol-category start-mark sym)
342 for reg = (when type
343 (let ((color (cdr (assq type lisp-colors))))
344 (when color
345 (coloring-region start-mark end-mark color))))
346 when reg collect reg
347 ;; if we're at start of a defining form, color the thing being defined.
348 when (and defn-color
349 (defining-symbol-p start-mark sym)
350 (form-offset (move-mark start-mark end-mark) 1)
351 (%scan-down-to-atom end-mark)
352 (mark< end-mark region-end))
353 collect (progn
354 (move-mark start-mark end-mark)
355 (unless (and (forward-form end-mark)
356 (mark<= end-mark region-end))
357 (move-mark end-mark region-end))
358 (unless (mark< start-mark end-mark)
359 (warn "definition got start ~s end ~s region-end ~s" start-mark end-mark
360 region-end)
361 (move-mark end-mark start-mark))
362 (coloring-region start-mark end-mark defn-color))
363 do (rotatef start-mark end-mark))))))
[15906]364
[15931]365(defun compute-unmatched-parens-coloring-in-region (start-mark end-mark)
[16446]366 ; not checking for lisp mode here because this is still useful even in text mode
[15931]367 (macrolet ((scan-loop (forwardp open-key buffer-start-mark start-line end-line close-key)
368 `(loop with paren-count = 0 with limit-line = (neighbor-line ,end-line ,forwardp) with in-region-p = nil
369 for line = (mark-line (,buffer-start-mark (mark-buffer m))) then (neighbor-line line ,forwardp)
370 until (eq line limit-line)
371 for info = (or (getf (line-plist line) 'lisp-info) (return nil))
372 as parens-on-line = ,(if forwardp '(lisp-info-net-close-parens info) '(lisp-info-net-open-parens info))
373 do (when (eq line ,start-line) (setq in-region-p t))
374 do (decf paren-count parens-on-line)
375 do (when (< paren-count 0)
376 (when in-region-p
377 ,(if forwardp '(line-start m line) '(line-end m line))
378 (loop with net-count = (+ paren-count parens-on-line) doing
379 (unless (scan-direction-valid m ,forwardp :lisp-syntax (or :close-paren :open-paren :newline))
380 (error "couldn't find ~s mismatches" (- paren-count)))
381 (ecase (character-attribute :lisp-syntax (direction-char m ,forwardp))
382 (,open-key (incf net-count))
383 (,close-key (when (< (decf net-count) 0)
384 (push (cons ,(if forwardp
385 '(mark-absolute-position m)
386 '(1- (mark-absolute-position m)))
387 coloring-data) result)
388 (when (eql (incf paren-count) 0) (return))
389 (setq net-count 0))))
390 (neighbor-mark m ,forwardp)))
391 (setq paren-count 0))
392 do (incf paren-count ,(if forwardp '(lisp-info-net-open-parens info) '(lisp-info-net-close-parens info))))))
393 (with-mark ((m start-mark))
394 (let* ((end-line (mark-line end-mark))
395 (start-line (mark-line start-mark))
396 (color (or (cdr (assq :unmatched-paren (cached-lisp-code-colors)))
397 (return-from compute-unmatched-parens-coloring-in-region nil)))
398 (coloring-data (cons 1 color))
399 (result nil))
400 (scan-loop t :open-paren buffer-start-mark start-line end-line :close-paren) ; Compute unmatched close parens, top down.
401 (scan-loop nil :close-paren buffer-end-mark end-line start-line :open-paren) ; Compute umatched open parens, bottom up.
402 result))))
403
[15906]404(defun compute-syntax-coloring-in-region (buffer start-pos end-pos)
405 (let* ((some-mark (buffer-point buffer)))
406 (with-mark ((start-mark some-mark)
407 (end-mark some-mark))
408 (unless (move-to-absolute-position start-mark start-pos)
409 (buffer-end start-mark))
410 (unless (move-to-absolute-position end-mark end-pos)
411 (buffer-end end-mark))
412 (assert (mark<= start-mark end-mark))
413 (when (mark< start-mark end-mark)
414 (pre-command-parse-check start-mark)
415 (sort (nconc (compute-string/comment-coloring-in-region start-mark end-mark)
[15931]416 (compute-symbol-coloring-in-region start-mark end-mark)
417 (compute-unmatched-parens-coloring-in-region start-mark end-mark))
[15906]418 #'< :key #'car)))))
419
Note: See TracBrowser for help on using the repository browser.