source: branches/acode-rewrite/source/cocoa-ide/hemlock/src/display.lisp

Last change on this file was 16082, checked in by Gary Byers, 11 years ago

Merge trunk changes into this branch. Expect some things to explode.

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