commit | author | age
|
5cb5f7
|
1 |
;;; magit-section.el --- section functionality -*- lexical-binding: t -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2010-2018 The Magit Project Contributors |
|
4 |
;; |
|
5 |
;; You should have received a copy of the AUTHORS.md file which |
|
6 |
;; lists all contributors. If not, see http://magit.vc/authors. |
|
7 |
|
|
8 |
;; Author: Jonas Bernoulli <jonas@bernoul.li> |
|
9 |
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> |
|
10 |
|
|
11 |
;; Magit is free software; you can redistribute it and/or modify it |
|
12 |
;; under the terms of the GNU General Public License as published by |
|
13 |
;; the Free Software Foundation; either version 3, or (at your option) |
|
14 |
;; any later version. |
|
15 |
;; |
|
16 |
;; Magit is distributed in the hope that it will be useful, but WITHOUT |
|
17 |
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|
18 |
;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
|
19 |
;; License for more details. |
|
20 |
;; |
|
21 |
;; You should have received a copy of the GNU General Public License |
|
22 |
;; along with Magit. If not, see http://www.gnu.org/licenses. |
|
23 |
|
|
24 |
;;; Commentary: |
|
25 |
|
|
26 |
;; This library implements "sections" as used in all Magit buffers. |
|
27 |
;; If you have used Magit before, then you probably know what that |
|
28 |
;; means, otherwise think "read-only Org-Mode for Git", kinda. |
|
29 |
|
|
30 |
;;; Code: |
|
31 |
|
|
32 |
(require 'cl-lib) |
|
33 |
(require 'dash) |
|
34 |
(require 'eieio) |
|
35 |
|
|
36 |
(eval-when-compile |
|
37 |
(require 'subr-x)) |
|
38 |
|
|
39 |
(require 'magit-utils) |
|
40 |
|
|
41 |
(declare-function magit-maybe-make-margin-overlay "magit-margin" ()) |
|
42 |
(declare-function magit-repository-local-get "magit-mode" |
|
43 |
(key &optional default repository)) |
|
44 |
(declare-function magit-repository-local-set "magit-mode" |
|
45 |
(key value &optional repository)) |
|
46 |
(defvar magit-keep-region-overlay) |
|
47 |
|
|
48 |
;;; Options |
|
49 |
|
|
50 |
(defgroup magit-section nil |
|
51 |
"Expandable sections." |
|
52 |
:link '(info-link "(magit)Sections") |
|
53 |
:group 'magit) |
|
54 |
|
|
55 |
(defcustom magit-section-show-child-count t |
|
56 |
"Whether to append the number of children to section headings. |
|
57 |
This only applies to sections for which doing so makes sense." |
|
58 |
:package-version '(magit . "2.1.0") |
|
59 |
:group 'magit-section |
|
60 |
:type 'boolean) |
|
61 |
|
|
62 |
(defcustom magit-section-movement-hook |
|
63 |
'(magit-hunk-set-window-start |
|
64 |
magit-log-maybe-update-revision-buffer |
|
65 |
magit-log-maybe-show-more-commits) |
|
66 |
"Hook run by `magit-section-goto'. |
|
67 |
That function in turn is used by all section movement commands." |
|
68 |
:package-version '(magit . "2.3.0") |
|
69 |
:group 'magit-section |
|
70 |
:type 'hook |
|
71 |
:options '(magit-hunk-set-window-start |
|
72 |
magit-status-maybe-update-revision-buffer |
|
73 |
magit-status-maybe-update-blob-buffer |
|
74 |
magit-log-maybe-update-revision-buffer |
|
75 |
magit-log-maybe-update-blob-buffer |
|
76 |
magit-log-maybe-show-more-commits)) |
|
77 |
|
|
78 |
(defcustom magit-section-highlight-hook |
|
79 |
'(magit-diff-highlight |
|
80 |
magit-section-highlight |
|
81 |
magit-section-highlight-selection) |
|
82 |
"Functions used to highlight the current section. |
|
83 |
Each function is run with the current section as only argument |
|
84 |
until one of them returns non-nil." |
|
85 |
:package-version '(magit . "2.1.0") |
|
86 |
:group 'magit-section |
|
87 |
:type 'hook |
|
88 |
:options '(magit-diff-highlight |
|
89 |
magit-section-highlight |
|
90 |
magit-section-highlight-selection)) |
|
91 |
|
|
92 |
(defcustom magit-section-unhighlight-hook |
|
93 |
'(magit-diff-unhighlight) |
|
94 |
"Functions used to unhighlight the previously current section. |
|
95 |
Each function is run with the current section as only argument |
|
96 |
until one of them returns non-nil. Most sections are properly |
|
97 |
unhighlighted without requiring a specialized unhighlighter, |
|
98 |
diff-related sections being the only exception." |
|
99 |
:package-version '(magit . "2.1.0") |
|
100 |
:group 'magit-section |
|
101 |
:type 'hook |
|
102 |
:options '(magit-diff-unhighlight)) |
|
103 |
|
|
104 |
(defcustom magit-section-set-visibility-hook |
|
105 |
'(magit-diff-expansion-threshold |
|
106 |
magit-section-cached-visibility) |
|
107 |
"Hook used to set the initial visibility of a section. |
|
108 |
Stop at the first function that returns non-nil. The returned |
|
109 |
value should be `show', `hide' or nil. If no function returns |
|
110 |
non-nil, determine the visibility as usual, i.e. use the |
|
111 |
hardcoded section specific default (see `magit-insert-section')." |
|
112 |
:package-version '(magit . "2.4.0") |
|
113 |
:group 'magit-section |
|
114 |
:type 'hook |
|
115 |
:options '(magit-diff-expansion-threshold |
|
116 |
magit-section-cached-visibility)) |
|
117 |
|
|
118 |
(defcustom magit-section-cache-visibility t |
|
119 |
"Whether to cache visibility of sections. |
|
120 |
|
|
121 |
Sections always retain their visibility state when they are being |
|
122 |
recreated during a refresh. But if a section disappears and then |
|
123 |
later reappears again, then this option controls whether this is |
|
124 |
the case. |
|
125 |
|
|
126 |
If t, then cache the visibility of all sections. If a list of |
|
127 |
section types, then only do so for matching sections. If nil, |
|
128 |
then don't do so for any sections." |
|
129 |
:package-version '(magit . "2.12.0") |
|
130 |
:group 'magit-section |
|
131 |
:type '(choice (const :tag "Don't cache visibility" nil) |
|
132 |
(const :tag "Cache visibility of all sections" t) |
|
133 |
(repeat :tag "Cache visibility for section types" symbol))) |
|
134 |
|
|
135 |
(defcustom magit-section-initial-visibility-alist |
|
136 |
'((stashes . hide)) |
|
137 |
"Alist controlling the initial visibility of sections. |
|
138 |
|
|
139 |
Each element maps a section type or lineage to the initial |
|
140 |
visibility state for such sections. The state has to be one of |
|
141 |
`show' or `hide', or a function that returns one of these symbols. |
|
142 |
A function is called with the section as the only argument. |
|
143 |
|
|
144 |
Use the command `magit-describe-section' to determine a section's |
|
145 |
lineage or type. The vector in the output is the section lineage |
|
146 |
and the type is the first element of that vector. Wildcards can |
|
147 |
be used, see `magit-section-match'. |
|
148 |
|
|
149 |
Currently this option is only used to override hardcoded defaults, |
|
150 |
but in the future it will also be used set the defaults. |
|
151 |
|
|
152 |
An entry whose key is `magit-status-initial-section' specifies |
|
153 |
the visibility of the section `magit-status-goto-initial-section' |
|
154 |
jumps to. This does not only override defaults, but also other |
|
155 |
entries of this alist." |
|
156 |
:package-version '(magit . "2.12.0") |
|
157 |
:group 'magit-section |
|
158 |
:type '(alist :key-type (sexp :tag "Section type/lineage") |
|
159 |
:value-type (choice (const hide) |
|
160 |
(const show) |
|
161 |
function))) |
|
162 |
|
|
163 |
(defface magit-section-highlight |
|
164 |
'((((class color) (background light)) :background "grey95") |
|
165 |
(((class color) (background dark)) :background "grey20")) |
|
166 |
"Face for highlighting the current section." |
|
167 |
:group 'magit-faces) |
|
168 |
|
|
169 |
(defface magit-section-heading |
|
170 |
'((((class color) (background light)) :foreground "DarkGoldenrod4" :weight bold) |
|
171 |
(((class color) (background dark)) :foreground "LightGoldenrod2" :weight bold)) |
|
172 |
"Face for section headings." |
|
173 |
:group 'magit-faces) |
|
174 |
|
|
175 |
(defface magit-section-secondary-heading '((t :weight bold)) |
|
176 |
"Face for section headings of some secondary headings." |
|
177 |
:group 'magit-faces) |
|
178 |
|
|
179 |
(defface magit-section-heading-selection |
|
180 |
'((((class color) (background light)) :foreground "salmon4") |
|
181 |
(((class color) (background dark)) :foreground "LightSalmon3")) |
|
182 |
"Face for selected section headings." |
|
183 |
:group 'magit-faces) |
|
184 |
|
|
185 |
;;; Classes |
|
186 |
|
|
187 |
(defvar magit--current-section-hook nil |
|
188 |
"Internal variable used for `magit-explain-section'.") |
|
189 |
|
|
190 |
(defvar magit--section-type-alist |
|
191 |
'( |
|
192 |
(file . magit-file-section) |
|
193 |
(hunk . magit-hunk-section) |
|
194 |
(module . magit-module-section) |
|
195 |
)) |
|
196 |
|
|
197 |
(defclass magit-section () |
|
198 |
((keymap :initform nil :allocation :class) |
|
199 |
(type :initform nil :initarg :type) |
|
200 |
(value :initform nil :initarg :value) |
|
201 |
(start :initform nil :initarg :start) |
|
202 |
(content :initform nil) |
|
203 |
(end :initform nil) |
|
204 |
(hidden :initform nil) |
|
205 |
(washer :initform nil) |
|
206 |
(process :initform nil) |
|
207 |
(heading-highlight-face :initform nil) |
|
208 |
(inserter :initform (symbol-value 'magit--current-section-hook)) |
|
209 |
(parent :initform nil :initarg :parent) |
|
210 |
(children :initform nil))) |
|
211 |
|
|
212 |
(defclass magit-file-section (magit-section) |
|
213 |
((source :initform nil) |
|
214 |
(header :initform nil))) |
|
215 |
|
|
216 |
(defclass magit-hunk-section (magit-section) |
|
217 |
((refined :initform nil))) |
|
218 |
|
|
219 |
(defclass magit-module-section (magit-file-section) |
|
220 |
()) |
|
221 |
|
|
222 |
;;; Core |
|
223 |
|
|
224 |
(defvar-local magit-root-section nil |
|
225 |
"The root section in the current buffer. |
|
226 |
All other sections are descendants of this section. The value |
|
227 |
of this variable is set by `magit-insert-section' and you should |
|
228 |
never modify it.") |
|
229 |
(put 'magit-root-section 'permanent-local t) |
|
230 |
|
|
231 |
(defun magit-current-section () |
|
232 |
"Return the section at point." |
|
233 |
(or (get-text-property (point) 'magit-section) magit-root-section)) |
|
234 |
|
|
235 |
(defun magit-section-ident (section) |
|
236 |
"Return an unique identifier for SECTION. |
|
237 |
The return value has the form ((TYPE . VALUE)...)." |
|
238 |
(with-slots (type value parent) section |
|
239 |
(cons (cons type |
|
240 |
(cond ((not (memq type '(unpulled unpushed))) value) |
|
241 |
((string-match-p "@{upstream}" value) value) |
|
242 |
;; Unfortunately Git chokes on "@{push}" when |
|
243 |
;; the value of `push.default' does not allow a |
|
244 |
;; 1:1 mapping. Arbitrary commands may consult |
|
245 |
;; the section value so we cannot use "@{push}". |
|
246 |
;; But `unpushed' and `unpulled' sections should |
|
247 |
;; keep their identity when switching branches |
|
248 |
;; so we have to use another value here. |
|
249 |
((string-match-p "\\`\\.\\." value) "..@{push}") |
|
250 |
(t "@{push}.."))) |
|
251 |
(and parent |
|
252 |
(magit-section-ident parent))))) |
|
253 |
|
|
254 |
(defun magit-get-section (ident &optional root) |
|
255 |
"Return the section identified by IDENT. |
|
256 |
IDENT has to be a list as returned by `magit-section-ident'." |
|
257 |
(setq ident (reverse ident)) |
|
258 |
(let ((section (or root magit-root-section))) |
|
259 |
(when (eq (car (pop ident)) |
|
260 |
(oref section type)) |
|
261 |
(while (and ident |
|
262 |
(setq section |
|
263 |
(--first |
|
264 |
(and (eq (caar ident) (oref it type)) |
|
265 |
(equal (cdar ident) (oref it value))) |
|
266 |
(oref section children)))) |
|
267 |
(pop ident)) |
|
268 |
section))) |
|
269 |
|
|
270 |
(defun magit-section-lineage (section) |
|
271 |
"Return the lineage of SECTION. |
|
272 |
The return value has the form (TYPE...)." |
|
273 |
(cons (oref section type) |
|
274 |
(when-let ((parent (oref section parent))) |
|
275 |
(magit-section-lineage parent)))) |
|
276 |
|
|
277 |
(defvar magit-insert-section--current nil "For internal use only.") |
|
278 |
(defvar magit-insert-section--parent nil "For internal use only.") |
|
279 |
(defvar magit-insert-section--oldroot nil "For internal use only.") |
|
280 |
|
|
281 |
;;; Commands |
|
282 |
;;;; Movement |
|
283 |
|
|
284 |
(defun magit-section-forward () |
|
285 |
"Move to the beginning of the next visible section." |
|
286 |
(interactive) |
|
287 |
(if (eobp) |
|
288 |
(user-error "No next section") |
|
289 |
(let ((section (magit-current-section))) |
|
290 |
(if (oref section parent) |
|
291 |
(let ((next (and (not (oref section hidden)) |
|
292 |
(not (= (oref section end) |
|
293 |
(1+ (point)))) |
|
294 |
(car (oref section children))))) |
|
295 |
(while (and section (not next)) |
|
296 |
(unless (setq next (car (magit-section-siblings section 'next))) |
|
297 |
(setq section (oref section parent)))) |
|
298 |
(if next |
|
299 |
(magit-section-goto next) |
|
300 |
(user-error "No next section"))) |
|
301 |
(magit-section-goto 1))))) |
|
302 |
|
|
303 |
(defun magit-section-backward () |
|
304 |
"Move to the beginning of the current or the previous visible section. |
|
305 |
When point is at the beginning of a section then move to the |
|
306 |
beginning of the previous visible section. Otherwise move to |
|
307 |
the beginning of the current section." |
|
308 |
(interactive) |
|
309 |
(if (bobp) |
|
310 |
(user-error "No previous section") |
|
311 |
(let ((section (magit-current-section)) children) |
|
312 |
(cond |
|
313 |
((and (= (point) |
|
314 |
(1- (oref section end))) |
|
315 |
(setq children (oref section children))) |
|
316 |
(magit-section-goto (car (last children)))) |
|
317 |
((and (oref section parent) |
|
318 |
(not (= (point) |
|
319 |
(oref section start)))) |
|
320 |
(magit-section-goto section)) |
|
321 |
(t |
|
322 |
(let ((prev (car (magit-section-siblings section 'prev)))) |
|
323 |
(if prev |
|
324 |
(while (and (not (oref prev hidden)) |
|
325 |
(setq children (oref prev children))) |
|
326 |
(setq prev (car (last children)))) |
|
327 |
(setq prev (oref section parent))) |
|
328 |
(cond (prev |
|
329 |
(magit-section-goto prev)) |
|
330 |
((oref section parent) |
|
331 |
(user-error "No previous section")) |
|
332 |
;; Eob special cases. |
|
333 |
((not (get-text-property (1- (point)) 'invisible)) |
|
334 |
(magit-section-goto -1)) |
|
335 |
(t |
|
336 |
(goto-char (previous-single-property-change |
|
337 |
(1- (point)) 'invisible)) |
|
338 |
(forward-line -1) |
|
339 |
(magit-section-goto (magit-current-section)))))))))) |
|
340 |
|
|
341 |
(defun magit-section-up () |
|
342 |
"Move to the beginning of the parent section." |
|
343 |
(interactive) |
|
344 |
(--if-let (oref (magit-current-section) parent) |
|
345 |
(magit-section-goto it) |
|
346 |
(user-error "No parent section"))) |
|
347 |
|
|
348 |
(defun magit-section-forward-sibling () |
|
349 |
"Move to the beginning of the next sibling section. |
|
350 |
If there is no next sibling section, then move to the parent." |
|
351 |
(interactive) |
|
352 |
(let ((current (magit-current-section))) |
|
353 |
(if (oref current parent) |
|
354 |
(--if-let (car (magit-section-siblings current 'next)) |
|
355 |
(magit-section-goto it) |
|
356 |
(magit-section-forward)) |
|
357 |
(magit-section-goto 1)))) |
|
358 |
|
|
359 |
(defun magit-section-backward-sibling () |
|
360 |
"Move to the beginning of the previous sibling section. |
|
361 |
If there is no previous sibling section, then move to the parent." |
|
362 |
(interactive) |
|
363 |
(let ((current (magit-current-section))) |
|
364 |
(if (oref current parent) |
|
365 |
(--if-let (car (magit-section-siblings current 'prev)) |
|
366 |
(magit-section-goto it) |
|
367 |
(magit-section-backward)) |
|
368 |
(magit-section-goto -1)))) |
|
369 |
|
|
370 |
(defun magit-section-goto (arg) |
|
371 |
(if (integerp arg) |
|
372 |
(progn (forward-line arg) |
|
373 |
(setq arg (magit-current-section))) |
|
374 |
(goto-char (oref arg start))) |
|
375 |
(run-hook-with-args 'magit-section-movement-hook arg)) |
|
376 |
|
|
377 |
(defun magit-section-set-window-start (section) |
|
378 |
"Ensure the beginning of SECTION is visible." |
|
379 |
(unless (pos-visible-in-window-p (oref section end)) |
|
380 |
(set-window-start (selected-window) (oref section start)))) |
|
381 |
|
|
382 |
(defun magit-hunk-set-window-start (section) |
|
383 |
"When SECTION is a `hunk', ensure that its beginning is visible. |
|
384 |
It the SECTION has a different type, then do nothing." |
|
385 |
(when (magit-hunk-section-p section) |
|
386 |
(magit-section-set-window-start section))) |
|
387 |
|
|
388 |
(defmacro magit-define-section-jumper (name heading type &optional value) |
|
389 |
"Define an interactive function to go some section. |
|
390 |
Together TYPE and VALUE identify the section. |
|
391 |
HEADING is the displayed heading of the section." |
|
392 |
(declare (indent defun)) |
|
393 |
`(defun ,name (&optional expand) ,(format "\ |
|
394 |
Jump to the section \"%s\". |
|
395 |
With a prefix argument also expand it." heading) |
|
396 |
(interactive "P") |
|
397 |
(--if-let (magit-get-section |
|
398 |
(cons (cons ',type ,value) |
|
399 |
(magit-section-ident magit-root-section))) |
|
400 |
(progn (goto-char (oref it start)) |
|
401 |
(when expand |
|
402 |
(with-local-quit (magit-section-show it)) |
|
403 |
(recenter 0))) |
|
404 |
(message ,(format "Section \"%s\" wasn't found" heading))))) |
|
405 |
|
|
406 |
;;;; Visibility |
|
407 |
|
|
408 |
(defun magit-section-show (section) |
|
409 |
"Show the body of the current section." |
|
410 |
(interactive (list (magit-current-section))) |
|
411 |
(oset section hidden nil) |
|
412 |
(when-let ((washer (oref section washer))) |
|
413 |
(oset section washer nil) |
|
414 |
(let ((inhibit-read-only t) |
|
415 |
(magit-insert-section--parent section) |
|
416 |
(content (oref section content))) |
|
417 |
(save-excursion |
|
418 |
(if (and content (< content (oref section end))) |
|
419 |
(funcall washer section) ; already partially washed (hunk) |
|
420 |
(goto-char (oref section end)) |
|
421 |
(oset section content (point-marker)) |
|
422 |
(funcall washer) |
|
423 |
(oset section end (point-marker))))) |
|
424 |
(magit-section-update-highlight)) |
|
425 |
(when-let ((beg (oref section content))) |
|
426 |
(remove-overlays beg (oref section end) 'invisible t)) |
|
427 |
(magit-section-maybe-cache-visibility section) |
|
428 |
(dolist (child (oref section children)) |
|
429 |
(if (oref child hidden) |
|
430 |
(magit-section-hide child) |
|
431 |
(magit-section-show child)))) |
|
432 |
|
|
433 |
(defun magit-section-hide (section) |
|
434 |
"Hide the body of the current section." |
|
435 |
(interactive (list (magit-current-section))) |
|
436 |
(if (eq section magit-root-section) |
|
437 |
(user-error "Cannot hide root section") |
|
438 |
(oset section hidden t) |
|
439 |
(when-let ((beg (oref section content))) |
|
440 |
(let ((end (oref section end))) |
|
441 |
(remove-overlays beg end 'invisible t) |
|
442 |
(let ((o (make-overlay beg end))) |
|
443 |
(overlay-put o 'evaporate t) |
|
444 |
(overlay-put o 'invisible t)))) |
|
445 |
(magit-section-maybe-cache-visibility section))) |
|
446 |
|
|
447 |
(defun magit-section-toggle (section) |
|
448 |
"Toggle visibility of the body of the current section." |
|
449 |
(interactive (list (magit-current-section))) |
|
450 |
(if (eq section magit-root-section) |
|
451 |
(user-error "Cannot hide root section") |
|
452 |
(goto-char (oref section start)) |
|
453 |
(if (oref section hidden) |
|
454 |
(magit-section-show section) |
|
455 |
(magit-section-hide section)))) |
|
456 |
|
|
457 |
(defun magit-section-toggle-children (section) |
|
458 |
"Toggle visibility of bodies of children of the current section." |
|
459 |
(interactive (list (magit-current-section))) |
|
460 |
(goto-char (oref section start)) |
|
461 |
(let* ((children (oref section children)) |
|
462 |
(show (--any-p (oref it hidden) children))) |
|
463 |
(dolist (c children) |
|
464 |
(oset c hidden show))) |
|
465 |
(magit-section-show section)) |
|
466 |
|
|
467 |
(defun magit-section-show-children (section &optional depth) |
|
468 |
"Recursively show the bodies of children of the current section. |
|
469 |
With a prefix argument show children that deep and hide deeper |
|
470 |
children." |
|
471 |
(interactive (list (magit-current-section))) |
|
472 |
(magit-section-show-children-1 section depth) |
|
473 |
(magit-section-show section)) |
|
474 |
|
|
475 |
(defun magit-section-show-children-1 (section &optional depth) |
|
476 |
(dolist (child (oref section children)) |
|
477 |
(oset child hidden nil) |
|
478 |
(if depth |
|
479 |
(if (> depth 0) |
|
480 |
(magit-section-show-children-1 child (1- depth)) |
|
481 |
(magit-section-hide child)) |
|
482 |
(magit-section-show-children-1 child)))) |
|
483 |
|
|
484 |
(defun magit-section-hide-children (section) |
|
485 |
"Recursively hide the bodies of children of the current section." |
|
486 |
(interactive (list (magit-current-section))) |
|
487 |
(mapc 'magit-section-hide (oref section children))) |
|
488 |
|
|
489 |
(defun magit-section-show-headings (section) |
|
490 |
"Recursively show headings of children of the current section. |
|
491 |
Only show the headings, previously shown text-only bodies are |
|
492 |
hidden." |
|
493 |
(interactive (list (magit-current-section))) |
|
494 |
(magit-section-show-headings-1 section) |
|
495 |
(magit-section-show section)) |
|
496 |
|
|
497 |
(defun magit-section-show-headings-1 (section) |
|
498 |
(dolist (child (oref section children)) |
|
499 |
(oset child hidden nil) |
|
500 |
(when (or (oref child children) |
|
501 |
(not (oref child content))) |
|
502 |
(magit-section-show-headings-1 child)))) |
|
503 |
|
|
504 |
(defun magit-section-cycle (section) |
|
505 |
"Cycle visibility of current section and its children." |
|
506 |
(interactive (list (magit-current-section))) |
|
507 |
(goto-char (oref section start)) |
|
508 |
(if (oref section hidden) |
|
509 |
(progn (magit-section-show section) |
|
510 |
(magit-section-hide-children section)) |
|
511 |
(let ((children (oref section children))) |
|
512 |
(cond ((and (--any-p (oref it hidden) children) |
|
513 |
(--any-p (oref it children) children)) |
|
514 |
(magit-section-show-headings section)) |
|
515 |
((-any-p 'magit-section-hidden-body children) |
|
516 |
(magit-section-show-children section)) |
|
517 |
(t |
|
518 |
(magit-section-hide section)))))) |
|
519 |
|
|
520 |
(defun magit-section-cycle-global () |
|
521 |
"Cycle visibility of all sections in the current buffer." |
|
522 |
(interactive) |
|
523 |
(let ((children (oref magit-root-section children))) |
|
524 |
(cond ((and (--any-p (oref it hidden) children) |
|
525 |
(--any-p (oref it children) children)) |
|
526 |
(magit-section-show-headings magit-root-section)) |
|
527 |
((-any-p 'magit-section-hidden-body children) |
|
528 |
(magit-section-show-children magit-root-section)) |
|
529 |
(t |
|
530 |
(mapc 'magit-section-hide children))))) |
|
531 |
|
|
532 |
(defun magit-section-cycle-diffs () |
|
533 |
"Cycle visibility of diff-related sections in the current buffer." |
|
534 |
(interactive) |
|
535 |
(when-let ((sections |
|
536 |
(cond ((derived-mode-p 'magit-status-mode) |
|
537 |
(--mapcat |
|
538 |
(when it |
|
539 |
(when (oref it hidden) |
|
540 |
(magit-section-show it)) |
|
541 |
(oref it children)) |
|
542 |
(list (magit-get-section '((staged) (status))) |
|
543 |
(magit-get-section '((unstaged) (status)))))) |
|
544 |
((derived-mode-p 'magit-diff-mode) |
|
545 |
(-filter #'magit-file-section-p |
|
546 |
(oref magit-root-section children)))))) |
|
547 |
(if (--any-p (oref it hidden) sections) |
|
548 |
(dolist (s sections) |
|
549 |
(magit-section-show s) |
|
550 |
(magit-section-hide-children s)) |
|
551 |
(let ((children (--mapcat (oref it children) sections))) |
|
552 |
(cond ((and (--any-p (oref it hidden) children) |
|
553 |
(--any-p (oref it children) children)) |
|
554 |
(mapc 'magit-section-show-headings sections)) |
|
555 |
((-any-p 'magit-section-hidden-body children) |
|
556 |
(mapc 'magit-section-show-children sections)) |
|
557 |
(t |
|
558 |
(mapc 'magit-section-hide sections))))))) |
|
559 |
|
|
560 |
(defun magit-section-hidden-body (section &optional pred) |
|
561 |
(--if-let (oref section children) |
|
562 |
(funcall (or pred '-any-p) 'magit-section-hidden-body it) |
|
563 |
(and (oref section content) |
|
564 |
(oref section hidden)))) |
|
565 |
|
|
566 |
(defun magit-section-invisible-p (section) |
|
567 |
"Return t if the SECTION's body is invisible. |
|
568 |
When the body of an ancestor of SECTION is collapsed then |
|
569 |
SECTION's body (and heading) obviously cannot be visible." |
|
570 |
(or (oref section hidden) |
|
571 |
(--when-let (oref section parent) |
|
572 |
(magit-section-invisible-p it)))) |
|
573 |
|
|
574 |
(defun magit-section-show-level (level) |
|
575 |
"Show surrounding sections up to LEVEL. |
|
576 |
If LEVEL is negative, show up to the absolute value. |
|
577 |
Sections at higher levels are hidden." |
|
578 |
(if (< level 0) |
|
579 |
(let ((s (magit-current-section))) |
|
580 |
(setq level (- level)) |
|
581 |
(while (> (1- (length (magit-section-ident s))) level) |
|
582 |
(setq s (oref s parent)) |
|
583 |
(goto-char (oref s start))) |
|
584 |
(magit-section-show-children magit-root-section (1- level))) |
|
585 |
(cl-do* ((s (magit-current-section) |
|
586 |
(oref s parent)) |
|
587 |
(i (1- (length (magit-section-ident s))) |
|
588 |
(cl-decf i))) |
|
589 |
((cond ((< i level) (magit-section-show-children s (- level i 1)) t) |
|
590 |
((= i level) (magit-section-hide s) t)) |
|
591 |
(magit-section-goto s))))) |
|
592 |
|
|
593 |
(defun magit-section-show-level-1 () |
|
594 |
"Show surrounding sections on first level." |
|
595 |
(interactive) |
|
596 |
(magit-section-show-level 1)) |
|
597 |
|
|
598 |
(defun magit-section-show-level-1-all () |
|
599 |
"Show all sections on first level." |
|
600 |
(interactive) |
|
601 |
(magit-section-show-level -1)) |
|
602 |
|
|
603 |
(defun magit-section-show-level-2 () |
|
604 |
"Show surrounding sections up to second level." |
|
605 |
(interactive) |
|
606 |
(magit-section-show-level 2)) |
|
607 |
|
|
608 |
(defun magit-section-show-level-2-all () |
|
609 |
"Show all sections up to second level." |
|
610 |
(interactive) |
|
611 |
(magit-section-show-level -2)) |
|
612 |
|
|
613 |
(defun magit-section-show-level-3 () |
|
614 |
"Show surrounding sections up to third level." |
|
615 |
(interactive) |
|
616 |
(magit-section-show-level 3)) |
|
617 |
|
|
618 |
(defun magit-section-show-level-3-all () |
|
619 |
"Show all sections up to third level." |
|
620 |
(interactive) |
|
621 |
(magit-section-show-level -3)) |
|
622 |
|
|
623 |
(defun magit-section-show-level-4 () |
|
624 |
"Show surrounding sections up to fourth level." |
|
625 |
(interactive) |
|
626 |
(magit-section-show-level 4)) |
|
627 |
|
|
628 |
(defun magit-section-show-level-4-all () |
|
629 |
"Show all sections up to fourth level." |
|
630 |
(interactive) |
|
631 |
(magit-section-show-level -4)) |
|
632 |
|
|
633 |
;;;; Auxiliary |
|
634 |
|
|
635 |
(defun magit-describe-section-briefly (section &optional message ident) |
|
636 |
"Show information about the section at point. |
|
637 |
With a prefix argument show the section identity instead of the |
|
638 |
section lineage. This command is intended for debugging purposes." |
|
639 |
(interactive (list (magit-current-section) t)) |
|
640 |
(let ((str (format "#<%s %S %S %s-%s>" |
|
641 |
(eieio-object-class section) |
|
642 |
(let ((val (oref section value))) |
|
643 |
(cond ((stringp val) |
|
644 |
(substring-no-properties val)) |
|
645 |
((and (eieio-object-p val) |
|
646 |
(fboundp 'cl-prin1-to-string)) |
|
647 |
(cl-prin1-to-string val)) |
|
648 |
(t |
|
649 |
val))) |
|
650 |
(if ident |
|
651 |
(magit-section-ident section) |
|
652 |
(apply #'vector (magit-section-lineage section))) |
|
653 |
(when-let ((m (oref section start))) |
|
654 |
(marker-position m)) |
|
655 |
(when-let ((m (oref section end))) |
|
656 |
(marker-position m))))) |
|
657 |
(if message (message "%s" str) str))) |
|
658 |
|
|
659 |
(cl-defmethod cl-print-object ((section magit-section) stream) |
|
660 |
"Print `magit-describe-section' result of SECTION." |
|
661 |
;; Used by debug and edebug as of Emacs 26. |
|
662 |
(princ (magit-describe-section-briefly section) stream)) |
|
663 |
|
|
664 |
(defun magit-describe-section (section &optional interactive-p) |
|
665 |
"Show information about the section at point." |
|
666 |
(interactive (list (magit-current-section) t)) |
|
667 |
(let ((inserter-section section)) |
|
668 |
(while (and inserter-section (not (oref inserter-section inserter))) |
|
669 |
(setq inserter-section (oref inserter-section parent))) |
|
670 |
(when (and inserter-section (oref inserter-section inserter)) |
|
671 |
(setq section inserter-section))) |
|
672 |
(pcase (oref section inserter) |
|
673 |
(`((,hook ,fun) . ,src-src) |
|
674 |
(help-setup-xref `(magit-describe-section ,section) interactive-p) |
|
675 |
(with-help-window (help-buffer) |
|
676 |
(with-current-buffer standard-output |
|
677 |
(insert (format-message |
|
678 |
"%s\n is inserted by `%s'\n from `%s'" |
|
679 |
(magit-describe-section-briefly section) |
|
680 |
(make-text-button (symbol-name fun) nil |
|
681 |
:type 'help-function |
|
682 |
'help-args (list fun)) |
|
683 |
(make-text-button (symbol-name hook) nil |
|
684 |
:type 'help-variable |
|
685 |
'help-args (list hook)))) |
|
686 |
(pcase-dolist (`(,hook ,fun) src-src) |
|
687 |
(insert (format-message |
|
688 |
",\n called by `%s'\n from `%s'" |
|
689 |
(make-text-button (symbol-name fun) nil |
|
690 |
:type 'help-function |
|
691 |
'help-args (list fun)) |
|
692 |
(make-text-button (symbol-name hook) nil |
|
693 |
:type 'help-variable |
|
694 |
'help-args (list hook))))) |
|
695 |
(insert ".\n\n") |
|
696 |
(insert |
|
697 |
(format-message |
|
698 |
"`%s' is " |
|
699 |
(make-text-button (symbol-name fun) nil |
|
700 |
:type 'help-function 'help-args (list fun)))) |
|
701 |
(describe-function-1 fun)))) |
|
702 |
(_ (message "%s, inserter unknown" |
|
703 |
(magit-describe-section-briefly section))))) |
|
704 |
|
|
705 |
;;; Match |
|
706 |
|
|
707 |
(cl-defun magit-section-match |
|
708 |
(condition &optional (section (magit-current-section))) |
|
709 |
"Return t if SECTION matches CONDITION. |
|
710 |
|
|
711 |
SECTION defaults to the section at point. If SECTION is not |
|
712 |
specified and there also is no section at point, then return |
|
713 |
nil. |
|
714 |
|
|
715 |
CONDITION can take the following forms: |
|
716 |
(CONDITION...) matches if any of the CONDITIONs matches. |
|
717 |
[CLASS...] matches if the section's class is the same |
|
718 |
as the first CLASS or a subclass of that; |
|
719 |
the section's parent class matches the |
|
720 |
second CLASS; and so on. |
|
721 |
[* CLASS...] matches sections that match [CLASS...] and |
|
722 |
also recursively all their child sections. |
|
723 |
CLASS matches if the section's class is the same |
|
724 |
as CLASS or a subclass of that; regardless |
|
725 |
of the classes of the parent sections. |
|
726 |
|
|
727 |
Each CLASS should be a class symbol, identifying a class that |
|
728 |
derives from `magit-section'. For backward compatibility CLASS |
|
729 |
can also be a \"type symbol\". A section matches such a symbol |
|
730 |
if the value of its `type' slot is `eq'. If a type symbol has |
|
731 |
an entry in `magit--section-type-alist', then a section also |
|
732 |
matches that type if its class is a subclass of the class that |
|
733 |
corresponds to the type as per that alist. |
|
734 |
|
|
735 |
Note that it is not necessary to specify the complete section |
|
736 |
lineage as printed by `magit-describe-section-briefly', unless |
|
737 |
of course you want to be that precise." |
|
738 |
(and section (magit-section-match-1 condition section))) |
|
739 |
|
|
740 |
(defun magit-section-match-1 (condition section) |
|
741 |
(cl-assert condition) |
|
742 |
(and section |
|
743 |
(if (listp condition) |
|
744 |
(--first (magit-section-match-1 it section) condition) |
|
745 |
(magit-section-match-2 (if (symbolp condition) |
|
746 |
(list condition) |
|
747 |
(cl-coerce condition 'list)) |
|
748 |
section)))) |
|
749 |
|
|
750 |
(defun magit-section-match-2 (condition section) |
|
751 |
(if (eq (car condition) '*) |
|
752 |
(or (magit-section-match-2 (cdr condition) section) |
|
753 |
(when-let ((parent (oref section parent))) |
|
754 |
(magit-section-match-2 condition parent))) |
|
755 |
(and (let ((c (car condition))) |
|
756 |
(if (class-p c) |
|
757 |
(cl-typep section c) |
|
758 |
(if-let ((class (cdr (assq c magit--section-type-alist)))) |
|
759 |
(cl-typep section class) |
|
760 |
(eq (oref section type) c)))) |
|
761 |
(or (not (setq condition (cdr condition))) |
|
762 |
(when-let ((parent (oref section parent))) |
|
763 |
(magit-section-match-2 condition parent)))))) |
|
764 |
|
|
765 |
(defun magit-section-value-if (condition &optional section) |
|
766 |
"If the section at point matches CONDITION, then return its value. |
|
767 |
|
|
768 |
If optional SECTION is non-nil then test whether that matches |
|
769 |
instead. If there is no section at point and SECTION is nil, |
|
770 |
then return nil. If the section does not match, then return |
|
771 |
nil. |
|
772 |
|
|
773 |
See `magit-section-match' for the forms CONDITION can take." |
|
774 |
(when-let ((section (or section (magit-current-section)))) |
|
775 |
(and (magit-section-match condition section) |
|
776 |
(oref section value)))) |
|
777 |
|
|
778 |
(defmacro magit-section-when (condition &rest body) |
|
779 |
"If the section at point matches CONDITION, evaluate BODY. |
|
780 |
|
|
781 |
If the section matches, then evaluate BODY forms sequentially |
|
782 |
with `it' bound to the section and return the value of the last |
|
783 |
form. If there are no BODY forms, then return the value of the |
|
784 |
section. If the section does not match or if there is no section |
|
785 |
at point, then return nil. |
|
786 |
|
|
787 |
See `magit-section-match' for the forms CONDITION can take." |
|
788 |
(declare (obsolete |
|
789 |
"instead use `magit-section-match' or `magit-section-value-if'." |
|
790 |
"Magit 2.90.0") |
|
791 |
(indent 1) |
|
792 |
(debug (sexp body))) |
|
793 |
`(--when-let (magit-current-section) |
|
794 |
;; Quoting CONDITION here often leads to double-quotes, which |
|
795 |
;; isn't an issue because `magit-section-match-1' implicitly |
|
796 |
;; deals with that. We shouldn't force users of this function |
|
797 |
;; to not quote CONDITION because that would needlessly break |
|
798 |
;; backward compatibility. |
|
799 |
(when (magit-section-match ',condition it) |
|
800 |
,@(or body '((oref it value)))))) |
|
801 |
|
|
802 |
(defmacro magit-section-case (&rest clauses) |
|
803 |
"Choose among clauses on the type of the section at point. |
|
804 |
|
|
805 |
Each clause looks like (CONDITION BODY...). The type of the |
|
806 |
section is compared against each CONDITION; the BODY forms of the |
|
807 |
first match are evaluated sequentially and the value of the last |
|
808 |
form is returned. Inside BODY the symbol `it' is bound to the |
|
809 |
section at point. If no clause succeeds or if there is no |
|
810 |
section at point, return nil. |
|
811 |
|
|
812 |
See `magit-section-match' for the forms CONDITION can take. |
|
813 |
Additionally a CONDITION of t is allowed in the final clause, and |
|
814 |
matches if no other CONDITION match, even if there is no section |
|
815 |
at point." |
|
816 |
(declare (indent 0) |
|
817 |
(debug (&rest (sexp body)))) |
|
818 |
`(let* ((it (magit-current-section))) |
|
819 |
(cond ,@(mapcar (lambda (clause) |
|
820 |
`(,(or (eq (car clause) t) |
|
821 |
`(and it |
|
822 |
(magit-section-match-1 ',(car clause) it))) |
|
823 |
,@(cdr clause))) |
|
824 |
clauses)))) |
|
825 |
|
|
826 |
(defun magit-section-match-assoc (section alist) |
|
827 |
"Return the value associated with SECTION's type or lineage in ALIST." |
|
828 |
(-some (pcase-lambda (`(,key . ,val)) |
|
829 |
(and (magit-section-match-1 key section) val)) |
|
830 |
alist)) |
|
831 |
|
|
832 |
;;; Create |
|
833 |
|
|
834 |
(defvar magit-insert-section-hook nil |
|
835 |
"Hook run after `magit-insert-section's BODY. |
|
836 |
Avoid using this hook and only ever do so if you know |
|
837 |
what you are doing and are sure there is no other way.") |
|
838 |
|
|
839 |
(defmacro magit-insert-section (&rest args) |
|
840 |
"Insert a section at point. |
|
841 |
|
|
842 |
TYPE is the section type, a symbol. Many commands that act on |
|
843 |
the current section behave differently depending on that type. |
|
844 |
Also if a variable `magit-TYPE-section-map' exists, then use |
|
845 |
that as the text-property `keymap' of all text belonging to the |
|
846 |
section (but this may be overwritten in subsections). TYPE can |
|
847 |
also have the form `(eval FORM)' in which case FORM is evaluated |
|
848 |
at runtime. |
|
849 |
|
|
850 |
Optional VALUE is the value of the section, usually a string |
|
851 |
that is required when acting on the section. |
|
852 |
|
|
853 |
When optional HIDE is non-nil collapse the section body by |
|
854 |
default, i.e. when first creating the section, but not when |
|
855 |
refreshing the buffer. Else expand it by default. This can be |
|
856 |
overwritten using `magit-section-set-visibility-hook'. When a |
|
857 |
section is recreated during a refresh, then the visibility of |
|
858 |
predecessor is inherited and HIDE is ignored (but the hook is |
|
859 |
still honored). |
|
860 |
|
|
861 |
BODY is any number of forms that actually insert the section's |
|
862 |
heading and body. Optional NAME, if specified, has to be a |
|
863 |
symbol, which is then bound to the struct of the section being |
|
864 |
inserted. |
|
865 |
|
|
866 |
Before BODY is evaluated the `start' of the section object is set |
|
867 |
to the value of `point' and after BODY was evaluated its `end' is |
|
868 |
set to the new value of `point'; BODY is responsible for moving |
|
869 |
`point' forward. |
|
870 |
|
|
871 |
If it turns out inside BODY that the section is empty, then |
|
872 |
`magit-cancel-section' can be used to abort and remove all traces |
|
873 |
of the partially inserted section. This can happen when creating |
|
874 |
a section by washing Git's output and Git didn't actually output |
|
875 |
anything this time around. |
|
876 |
|
|
877 |
\(fn [NAME] (TYPE &optional VALUE HIDE) &rest BODY)" |
|
878 |
(declare (indent defun) |
|
879 |
(debug ([&optional symbolp] |
|
880 |
(&or [("eval" symbolp) &optional form form] |
|
881 |
[symbolp &optional form form]) |
|
882 |
body))) |
|
883 |
(let ((tp (cl-gensym "type")) |
|
884 |
(s* (and (symbolp (car args)) |
|
885 |
(pop args))) |
|
886 |
(s (cl-gensym "section"))) |
|
887 |
`(let* ((,tp ,(let ((type (nth 0 (car args)))) |
|
888 |
(if (eq (car-safe type) 'eval) |
|
889 |
(cadr type) |
|
890 |
`',type))) |
|
891 |
(,s (funcall (if (class-p ,tp) |
|
892 |
,tp |
|
893 |
(or (cdr (assq ,tp magit--section-type-alist)) |
|
894 |
'magit-section)) |
|
895 |
:type |
|
896 |
(if (class-p ,tp) |
|
897 |
(or (car (rassq ,tp magit--section-type-alist)) |
|
898 |
(error "BUG: No entry for %s in %s" ,tp |
|
899 |
'magit--section-type-alist)) |
|
900 |
,tp) |
|
901 |
:value ,(nth 1 (car args)) |
|
902 |
:start (point-marker) |
|
903 |
:parent magit-insert-section--parent))) |
|
904 |
(oset ,s hidden |
|
905 |
(if-let ((value (run-hook-with-args-until-success |
|
906 |
'magit-section-set-visibility-hook ,s))) |
|
907 |
(eq value 'hide) |
|
908 |
(if-let ((incarnation (and magit-insert-section--oldroot |
|
909 |
(magit-get-section |
|
910 |
(magit-section-ident ,s) |
|
911 |
magit-insert-section--oldroot)))) |
|
912 |
(oref incarnation hidden) |
|
913 |
(if-let ((value (magit-section-match-assoc |
|
914 |
,s magit-section-initial-visibility-alist))) |
|
915 |
(progn |
|
916 |
(when (functionp value) |
|
917 |
(setq value (funcall value ,s))) |
|
918 |
(eq value 'hide)) |
|
919 |
,(nth 2 (car args)))))) |
|
920 |
(let ((magit-insert-section--current ,s) |
|
921 |
(magit-insert-section--parent ,s) |
|
922 |
(magit-insert-section--oldroot |
|
923 |
(or magit-insert-section--oldroot |
|
924 |
(unless magit-insert-section--parent |
|
925 |
(prog1 magit-root-section |
|
926 |
(setq magit-root-section ,s)))))) |
|
927 |
(catch 'cancel-section |
|
928 |
,@(if s* |
|
929 |
`((let ((,s* ,s)) |
|
930 |
,@(cdr args))) |
|
931 |
(cdr args)) |
|
932 |
;; `magit-insert-section-hook' should *not* be run with |
|
933 |
;; `magit-run-section-hook' because it's a hook that runs |
|
934 |
;; on section insertion, not a section inserting hook. |
|
935 |
(run-hooks 'magit-insert-section-hook) |
|
936 |
(magit-insert-child-count ,s) |
|
937 |
(set-marker-insertion-type (oref ,s start) t) |
|
938 |
(let* ((end (oset ,s end (point-marker))) |
|
939 |
(class-map (oref-default ,s keymap)) |
|
940 |
(magit-map (intern (format "magit-%s-section-map" |
|
941 |
(oref ,s type)))) |
|
942 |
(forge-map (intern (format "forge-%s-section-map" |
|
943 |
(oref ,s type)))) |
|
944 |
(map (or (and class-map (symbol-value class-map)) |
|
945 |
(and (boundp magit-map) (symbol-value magit-map)) |
|
946 |
(and (boundp forge-map) (symbol-value forge-map))))) |
|
947 |
(save-excursion |
|
948 |
(goto-char (oref ,s start)) |
|
949 |
(while (< (point) end) |
|
950 |
(let ((next (or (next-single-property-change |
|
951 |
(point) 'magit-section) |
|
952 |
end))) |
|
953 |
(unless (get-text-property (point) 'magit-section) |
|
954 |
(put-text-property (point) next 'magit-section ,s) |
|
955 |
(when map |
|
956 |
(put-text-property (point) next 'keymap map))) |
|
957 |
(goto-char next))))) |
|
958 |
(if (eq ,s magit-root-section) |
|
959 |
(let ((magit-section-cache-visibility nil)) |
|
960 |
(magit-section-show ,s)) |
|
961 |
(oset (oref ,s parent) children |
|
962 |
(nconc (oref (oref ,s parent) children) |
|
963 |
(list ,s))))) |
|
964 |
,s)))) |
|
965 |
|
|
966 |
(defun magit-cancel-section () |
|
967 |
(when magit-insert-section--current |
|
968 |
(if (not (oref magit-insert-section--current parent)) |
|
969 |
(insert "(empty)\n") |
|
970 |
(delete-region (oref magit-insert-section--current start) |
|
971 |
(point)) |
|
972 |
(setq magit-insert-section--current nil) |
|
973 |
(throw 'cancel-section nil)))) |
|
974 |
|
|
975 |
(defun magit-insert-heading (&rest args) |
|
976 |
"Insert the heading for the section currently being inserted. |
|
977 |
|
|
978 |
This function should only be used inside `magit-insert-section'. |
|
979 |
|
|
980 |
When called without any arguments, then just set the `content' |
|
981 |
slot of the object representing the section being inserted to |
|
982 |
a marker at `point'. The section should only contain a single |
|
983 |
line when this function is used like this. |
|
984 |
|
|
985 |
When called with arguments ARGS, which have to be strings, or |
|
986 |
nil, then insert those strings at point. The section should not |
|
987 |
contain any text before this happens and afterwards it should |
|
988 |
again only contain a single line. If the `face' property is set |
|
989 |
anywhere inside any of these strings, then insert all of them |
|
990 |
unchanged. Otherwise use the `magit-section-heading' face for |
|
991 |
all inserted text. |
|
992 |
|
|
993 |
The `content' property of the section struct is the end of the |
|
994 |
heading (which lasts from `start' to `content') and the beginning |
|
995 |
of the the body (which lasts from `content' to `end'). If the |
|
996 |
value of `content' is nil, then the section has no heading and |
|
997 |
its body cannot be collapsed. If a section does have a heading, |
|
998 |
then its height must be exactly one line, including a trailing |
|
999 |
newline character. This isn't enforced, you are responsible for |
|
1000 |
getting it right. The only exception is that this function does |
|
1001 |
insert a newline character if necessary." |
|
1002 |
(declare (indent defun)) |
|
1003 |
(when args |
|
1004 |
(let ((heading (apply #'concat args))) |
|
1005 |
(insert (if (text-property-not-all 0 (length heading) 'face nil heading) |
|
1006 |
heading |
|
1007 |
(propertize heading 'face 'magit-section-heading))))) |
|
1008 |
(unless (bolp) |
|
1009 |
(insert ?\n)) |
|
1010 |
(magit-maybe-make-margin-overlay) |
|
1011 |
(oset magit-insert-section--current content (point-marker))) |
|
1012 |
|
|
1013 |
(defun magit-insert-headers (hook) |
|
1014 |
(let* ((header-sections nil) |
|
1015 |
(magit-insert-section-hook |
|
1016 |
(cons (lambda () |
|
1017 |
(push magit-insert-section--current |
|
1018 |
header-sections)) |
|
1019 |
(if (listp magit-insert-section-hook) |
|
1020 |
magit-insert-section-hook |
|
1021 |
(list magit-insert-section-hook))))) |
|
1022 |
(magit-run-section-hook hook) |
|
1023 |
(when header-sections |
|
1024 |
(insert "\n") |
|
1025 |
;; Make the first header into the parent of the rest. |
|
1026 |
(when (cdr header-sections) |
|
1027 |
(cl-callf nreverse header-sections) |
|
1028 |
(let* ((1st-header (pop header-sections)) |
|
1029 |
(header-parent (oref 1st-header parent))) |
|
1030 |
(oset header-parent children (list 1st-header)) |
|
1031 |
(oset 1st-header children header-sections) |
|
1032 |
(oset 1st-header content (oref (car header-sections) start)) |
|
1033 |
(oset 1st-header end (oref (car (last header-sections)) end)) |
|
1034 |
(dolist (sub-header header-sections) |
|
1035 |
(oset sub-header parent 1st-header))))))) |
|
1036 |
|
|
1037 |
(defun magit-insert-child-count (section) |
|
1038 |
"Modify SECTION's heading to contain number of child sections. |
|
1039 |
|
|
1040 |
If `magit-section-show-child-count' is non-nil and the SECTION |
|
1041 |
has children and its heading ends with \":\", then replace that |
|
1042 |
with \" (N)\", where N is the number of child sections. |
|
1043 |
|
|
1044 |
This function is called by `magit-insert-section' after that has |
|
1045 |
evaluated its BODY. Admittedly that's a bit of a hack." |
|
1046 |
;; This has to be fast, not pretty! |
|
1047 |
(let (content count) |
|
1048 |
(when (and magit-section-show-child-count |
|
1049 |
(setq count (length (oref section children))) |
|
1050 |
(> count 0) |
|
1051 |
(setq content (oref section content)) |
|
1052 |
(eq (char-before (1- content)) ?:)) |
|
1053 |
(save-excursion |
|
1054 |
(goto-char (- content 2)) |
|
1055 |
(insert (format " (%s)" count)) |
|
1056 |
(delete-char 1))))) |
|
1057 |
|
|
1058 |
;;; Update |
|
1059 |
|
|
1060 |
(defvar-local magit-section-highlight-overlays nil) |
|
1061 |
(defvar-local magit-section-highlighted-section nil) |
|
1062 |
(defvar-local magit-section-highlighted-sections nil) |
|
1063 |
(defvar-local magit-section-unhighlight-sections nil) |
|
1064 |
|
|
1065 |
(defun magit-section-update-region (_) |
|
1066 |
"When the region is a valid section-selection, highlight them all." |
|
1067 |
;; At least that's what it does conceptually. In actuality it just |
|
1068 |
;; returns a list of those sections, and it doesn't even matter if |
|
1069 |
;; this is a member of `magit-region-highlight-hook'. It probably |
|
1070 |
;; should be removed, but I want to make sure before removing it. |
|
1071 |
(magit-region-sections)) |
|
1072 |
|
|
1073 |
(defun magit-section-update-highlight () |
|
1074 |
(let ((section (magit-current-section))) |
|
1075 |
(unless (eq section magit-section-highlighted-section) |
|
1076 |
(let ((inhibit-read-only t) |
|
1077 |
(deactivate-mark nil) |
|
1078 |
(selection (magit-region-sections))) |
|
1079 |
(mapc #'delete-overlay magit-section-highlight-overlays) |
|
1080 |
(setq magit-section-highlight-overlays nil) |
|
1081 |
(setq magit-section-unhighlight-sections |
|
1082 |
magit-section-highlighted-sections) |
|
1083 |
(setq magit-section-highlighted-sections nil) |
|
1084 |
(unless (eq section magit-root-section) |
|
1085 |
(run-hook-with-args-until-success |
|
1086 |
'magit-section-highlight-hook section selection)) |
|
1087 |
(dolist (s magit-section-unhighlight-sections) |
|
1088 |
(run-hook-with-args-until-success |
|
1089 |
'magit-section-unhighlight-hook s selection)) |
|
1090 |
(restore-buffer-modified-p nil) |
|
1091 |
(unless (eq magit-section-highlighted-section section) |
|
1092 |
(setq magit-section-highlighted-section |
|
1093 |
(and (not (oref section hidden)) |
|
1094 |
section)))) |
|
1095 |
(when (version< emacs-version "25.1") |
|
1096 |
(setq deactivate-mark nil))))) |
|
1097 |
|
|
1098 |
(defun magit-section-highlight (section selection) |
|
1099 |
"Highlight SECTION and if non-nil all sections in SELECTION. |
|
1100 |
This function works for any section but produces undesirable |
|
1101 |
effects for diff related sections, which by default are |
|
1102 |
highlighted using `magit-diff-highlight'. Return t." |
|
1103 |
(when-let ((face (oref section heading-highlight-face))) |
|
1104 |
(dolist (section (or selection (list section))) |
|
1105 |
(magit-section-make-overlay |
|
1106 |
(oref section start) |
|
1107 |
(or (oref section content) |
|
1108 |
(oref section end)) |
|
1109 |
face))) |
|
1110 |
(cond (selection |
|
1111 |
(magit-section-make-overlay (oref (car selection) start) |
|
1112 |
(oref (car (last selection)) end) |
|
1113 |
'magit-section-highlight) |
|
1114 |
(magit-section-highlight-selection nil selection)) |
|
1115 |
(t |
|
1116 |
(magit-section-make-overlay (oref section start) |
|
1117 |
(oref section end) |
|
1118 |
'magit-section-highlight))) |
|
1119 |
t) |
|
1120 |
|
|
1121 |
(defun magit-section-highlight-selection (_ selection) |
|
1122 |
"Highlight the section-selection region. |
|
1123 |
If SELECTION is non-nil, then it is a list of sections selected by |
|
1124 |
the region. The headings of these sections are then highlighted. |
|
1125 |
|
|
1126 |
This is a fallback for people who don't want to highlight the |
|
1127 |
current section and therefore removed `magit-section-highlight' |
|
1128 |
from `magit-section-highlight-hook'. |
|
1129 |
|
|
1130 |
This function is necessary to ensure that a representation of |
|
1131 |
such a region is visible. If neither of these functions were |
|
1132 |
part of the hook variable, then such a region would be |
|
1133 |
invisible." |
|
1134 |
(when (and selection |
|
1135 |
(not (and (eq this-command 'mouse-drag-region)))) |
|
1136 |
(dolist (section selection) |
|
1137 |
(magit-section-make-overlay (oref section start) |
|
1138 |
(or (oref section content) |
|
1139 |
(oref section end)) |
|
1140 |
'magit-section-heading-selection)) |
|
1141 |
t)) |
|
1142 |
|
|
1143 |
(defun magit-section-make-overlay (start end face) |
|
1144 |
;; Yes, this doesn't belong here. But the alternative of |
|
1145 |
;; spreading this hack across the code base is even worse. |
|
1146 |
(when (and magit-keep-region-overlay |
|
1147 |
(memq face '(magit-section-heading-selection |
|
1148 |
magit-diff-file-heading-selection |
|
1149 |
magit-diff-hunk-heading-selection))) |
|
1150 |
(setq face (list :foreground (face-foreground face)))) |
|
1151 |
(let ((ov (make-overlay start end nil t))) |
|
1152 |
(overlay-put ov 'face face) |
|
1153 |
(overlay-put ov 'evaporate t) |
|
1154 |
(push ov magit-section-highlight-overlays) |
|
1155 |
ov)) |
|
1156 |
|
|
1157 |
(defun magit-section-goto-successor (section line char arg) |
|
1158 |
(let ((ident (magit-section-ident section))) |
|
1159 |
(--if-let (magit-get-section ident) |
|
1160 |
(let ((start (oref it start))) |
|
1161 |
(goto-char start) |
|
1162 |
(unless (eq it magit-root-section) |
|
1163 |
(ignore-errors |
|
1164 |
(forward-line line) |
|
1165 |
(forward-char char)) |
|
1166 |
(unless (eq (magit-current-section) it) |
|
1167 |
(goto-char start)))) |
|
1168 |
(or (and (magit-hunk-section-p section) |
|
1169 |
(when-let ((parent (magit-get-section |
|
1170 |
(magit-section-ident |
|
1171 |
(oref section parent))))) |
|
1172 |
(let* ((children (oref parent children)) |
|
1173 |
(siblings (magit-section-siblings section 'prev)) |
|
1174 |
(previous (nth (length siblings) children))) |
|
1175 |
(if (not arg) |
|
1176 |
(--when-let (or previous (car (last children))) |
|
1177 |
(magit-section-goto it) |
|
1178 |
t) |
|
1179 |
(when previous |
|
1180 |
(magit-section-goto previous)) |
|
1181 |
(if (and (stringp arg) |
|
1182 |
(re-search-forward arg (oref parent end) t)) |
|
1183 |
(goto-char (match-beginning 0)) |
|
1184 |
(goto-char (oref (car (last children)) end)) |
|
1185 |
(forward-line -1) |
|
1186 |
(while (looking-at "^ ") (forward-line -1)) |
|
1187 |
(while (looking-at "^[-+]") (forward-line -1)) |
|
1188 |
(forward-line)))))) |
|
1189 |
(goto-char (--if-let (magit-section-goto-successor-1 section) |
|
1190 |
(if (eq (oref it type) 'button) |
|
1191 |
(point-min) |
|
1192 |
(oref it start)) |
|
1193 |
(point-min))))))) |
|
1194 |
|
|
1195 |
(defun magit-section-goto-successor-1 (section) |
|
1196 |
(or (--when-let (pcase (oref section type) |
|
1197 |
(`staged 'unstaged) |
|
1198 |
(`unstaged 'staged) |
|
1199 |
(`unpushed 'unpulled) |
|
1200 |
(`unpulled 'unpushed)) |
|
1201 |
(magit-get-section `((,it) (status)))) |
|
1202 |
(--when-let (car (magit-section-siblings section 'next)) |
|
1203 |
(magit-get-section (magit-section-ident it))) |
|
1204 |
(--when-let (car (magit-section-siblings section 'prev)) |
|
1205 |
(magit-get-section (magit-section-ident it))) |
|
1206 |
(--when-let (oref section parent) |
|
1207 |
(or (magit-get-section (magit-section-ident it)) |
|
1208 |
(magit-section-goto-successor-1 it))))) |
|
1209 |
|
|
1210 |
;;; Visibility |
|
1211 |
|
|
1212 |
(defvar-local magit-section-visibility-cache nil) |
|
1213 |
(put 'magit-section-visibility-cache 'permanent-local t) |
|
1214 |
|
|
1215 |
(defun magit-section-cached-visibility (section) |
|
1216 |
"Set SECTION's visibility to the cached value." |
|
1217 |
(cdr (assoc (magit-section-ident section) |
|
1218 |
magit-section-visibility-cache))) |
|
1219 |
|
|
1220 |
(cl-defun magit-section-cache-visibility |
|
1221 |
(&optional (section magit-insert-section--current)) |
|
1222 |
;; Emacs 25's `alist-get' lacks TESTFN. |
|
1223 |
(let* ((id (magit-section-ident section)) |
|
1224 |
(elt (assoc id magit-section-visibility-cache)) |
|
1225 |
(val (if (oref section hidden) 'hide 'show))) |
|
1226 |
(if elt |
|
1227 |
(setcdr elt val) |
|
1228 |
(push (cons id val) magit-section-visibility-cache)))) |
|
1229 |
|
|
1230 |
(cl-defun magit-section-maybe-cache-visibility |
|
1231 |
(&optional (section magit-insert-section--current)) |
|
1232 |
(when (or (eq magit-section-cache-visibility t) |
|
1233 |
(memq (oref section type) |
|
1234 |
magit-section-cache-visibility)) |
|
1235 |
(magit-section-cache-visibility section))) |
|
1236 |
|
|
1237 |
(defun magit-preserve-section-visibility-cache () |
|
1238 |
(when (derived-mode-p 'magit-status-mode 'magit-refs-mode) |
|
1239 |
(magit-repository-local-set |
|
1240 |
(cons major-mode 'magit-section-visibility-cache) |
|
1241 |
magit-section-visibility-cache))) |
|
1242 |
|
|
1243 |
(defun magit-restore-section-visibility-cache (mode) |
|
1244 |
(setq magit-section-visibility-cache |
|
1245 |
(magit-repository-local-get |
|
1246 |
(cons mode 'magit-section-visibility-cache)))) |
|
1247 |
|
|
1248 |
;;; Utilities |
|
1249 |
|
|
1250 |
(cl-defun magit-section-selected-p (section &optional (selection nil sselection)) |
|
1251 |
(and (not (eq section magit-root-section)) |
|
1252 |
(or (eq section (magit-current-section)) |
|
1253 |
(memq section (if sselection |
|
1254 |
selection |
|
1255 |
(setq selection (magit-region-sections)))) |
|
1256 |
(--when-let (oref section parent) |
|
1257 |
(magit-section-selected-p it selection))))) |
|
1258 |
|
|
1259 |
(defun magit-section-parent-value (section) |
|
1260 |
(when-let ((parent (oref section parent))) |
|
1261 |
(oref parent value))) |
|
1262 |
|
|
1263 |
(defun magit-section-siblings (section &optional direction) |
|
1264 |
"Return a list of the sibling sections of SECTION. |
|
1265 |
|
|
1266 |
If optional DIRECTION is `prev', then return siblings that come |
|
1267 |
before SECTION. If it is `next', then return siblings that come |
|
1268 |
after SECTION. For all other values, return all siblings |
|
1269 |
excluding SECTION itself." |
|
1270 |
(when-let ((parent (oref section parent))) |
|
1271 |
(let ((siblings (oref parent children))) |
|
1272 |
(pcase direction |
|
1273 |
(`prev (cdr (member section (reverse siblings)))) |
|
1274 |
(`next (cdr (member section siblings))) |
|
1275 |
(_ (remq section siblings)))))) |
|
1276 |
|
|
1277 |
(defun magit-region-values (&optional condition multiple) |
|
1278 |
"Return a list of the values of the selected sections. |
|
1279 |
|
|
1280 |
Return the values that themselves would be returned by |
|
1281 |
`magit-region-sections' (which see)." |
|
1282 |
(--map (oref it value) |
|
1283 |
(magit-region-sections condition multiple))) |
|
1284 |
|
|
1285 |
(defun magit-region-sections (&optional condition multiple) |
|
1286 |
"Return a list of the selected sections. |
|
1287 |
|
|
1288 |
When the region is active and constitutes a valid section |
|
1289 |
selection, then return a list of all selected sections. This is |
|
1290 |
the case when the region begins in the heading of a section and |
|
1291 |
ends in the heading of the same section or in that of a sibling |
|
1292 |
section. If optional MULTIPLE is non-nil, then the region cannot |
|
1293 |
begin and end in the same section. |
|
1294 |
|
|
1295 |
When the selection is not valid, then return nil. In this case, |
|
1296 |
most commands that can act on the selected sections will instead |
|
1297 |
act on the section at point. |
|
1298 |
|
|
1299 |
When the region looks like it would in any other buffer then |
|
1300 |
the selection is invalid. When the selection is valid then the |
|
1301 |
region uses the `magit-section-highlight' face. This does not |
|
1302 |
apply to diffs where things get a bit more complicated, but even |
|
1303 |
here if the region looks like it usually does, then that's not |
|
1304 |
a valid selection as far as this function is concerned. |
|
1305 |
|
|
1306 |
If optional CONDITION is non-nil, then the selection not only |
|
1307 |
has to be valid; all selected sections additionally have to match |
|
1308 |
CONDITION, or nil is returned. See `magit-section-match' for the |
|
1309 |
forms CONDITION can take." |
|
1310 |
(when (region-active-p) |
|
1311 |
(let* ((rbeg (region-beginning)) |
|
1312 |
(rend (region-end)) |
|
1313 |
(sbeg (get-text-property rbeg 'magit-section)) |
|
1314 |
(send (get-text-property rend 'magit-section))) |
|
1315 |
(when (and send |
|
1316 |
(not (eq send magit-root-section)) |
|
1317 |
(not (and multiple (eq send sbeg)))) |
|
1318 |
(let ((siblings (cons sbeg (magit-section-siblings sbeg 'next))) |
|
1319 |
sections) |
|
1320 |
(when (and (memq send siblings) |
|
1321 |
(magit-section-position-in-heading-p sbeg rbeg) |
|
1322 |
(magit-section-position-in-heading-p send rend)) |
|
1323 |
(while siblings |
|
1324 |
(push (car siblings) sections) |
|
1325 |
(when (eq (pop siblings) send) |
|
1326 |
(setq siblings nil))) |
|
1327 |
(setq sections (nreverse sections)) |
|
1328 |
(when (or (not condition) |
|
1329 |
(--all-p (magit-section-match condition it) sections)) |
|
1330 |
sections))))))) |
|
1331 |
|
|
1332 |
(defun magit-section-position-in-heading-p (&optional section pos) |
|
1333 |
"Return t if POSITION is inside the heading of SECTION. |
|
1334 |
POSITION defaults to point and SECTION defaults to the |
|
1335 |
current section." |
|
1336 |
(unless section |
|
1337 |
(setq section (magit-current-section))) |
|
1338 |
(unless pos |
|
1339 |
(setq pos (point))) |
|
1340 |
(and section |
|
1341 |
(>= pos (oref section start)) |
|
1342 |
(< pos (or (oref section content) |
|
1343 |
(oref section end))) |
|
1344 |
t)) |
|
1345 |
|
|
1346 |
(defun magit-section-internal-region-p (&optional section) |
|
1347 |
"Return t if the region is active and inside SECTION's body. |
|
1348 |
If optional SECTION is nil, use the current section." |
|
1349 |
(and (region-active-p) |
|
1350 |
(or section (setq section (magit-current-section))) |
|
1351 |
(let ((beg (get-text-property (region-beginning) 'magit-section))) |
|
1352 |
(and (eq beg (get-text-property (region-end) 'magit-section)) |
|
1353 |
(eq beg section))) |
|
1354 |
(not (or (magit-section-position-in-heading-p section (region-beginning)) |
|
1355 |
(magit-section-position-in-heading-p section (region-end)))) |
|
1356 |
t)) |
|
1357 |
|
|
1358 |
(defun magit-section--backward-protected () |
|
1359 |
"Move to the beginning of the current or the previous visible section. |
|
1360 |
Same as `magit-section-backward' but for non-interactive use. |
|
1361 |
Suppress `magit-section-movement-hook', and return a boolean to |
|
1362 |
indicate whether a section was found, instead of raising an error |
|
1363 |
if not." |
|
1364 |
(condition-case nil |
|
1365 |
(let ((magit-section-movement-hook nil)) |
|
1366 |
(magit-section-backward) |
|
1367 |
t) |
|
1368 |
(user-error nil))) |
|
1369 |
|
|
1370 |
(defun magit-section--backward-find (predicate) |
|
1371 |
"Move to the first previous section satisfying PREDICATE. |
|
1372 |
PREDICATE does not take any parameter and should not move |
|
1373 |
point." |
|
1374 |
(let (found) |
|
1375 |
(while (and (setq found (magit-section--backward-protected)) |
|
1376 |
(not (funcall predicate)))) |
|
1377 |
found)) |
|
1378 |
|
|
1379 |
(defun magit-wash-sequence (function) |
|
1380 |
"Repeatedly call FUNCTION until it returns nil or eob is reached. |
|
1381 |
FUNCTION has to move point forward or return nil." |
|
1382 |
(while (and (not (eobp)) (funcall function)))) |
|
1383 |
|
|
1384 |
(defun magit-add-section-hook (hook function &optional at append local) |
|
1385 |
"Add to the value of section hook HOOK the function FUNCTION. |
|
1386 |
|
|
1387 |
Add FUNCTION at the beginning of the hook list unless optional |
|
1388 |
APPEND is non-nil, in which case FUNCTION is added at the end. |
|
1389 |
If FUNCTION already is a member, then move it to the new location. |
|
1390 |
|
|
1391 |
If optional AT is non-nil and a member of the hook list, then |
|
1392 |
add FUNCTION next to that instead. Add before or after AT, or |
|
1393 |
replace AT with FUNCTION depending on APPEND. If APPEND is the |
|
1394 |
symbol `replace', then replace AT with FUNCTION. For any other |
|
1395 |
non-nil value place FUNCTION right after AT. If nil, then place |
|
1396 |
FUNCTION right before AT. If FUNCTION already is a member of the |
|
1397 |
list but AT is not, then leave FUNCTION where ever it already is. |
|
1398 |
|
|
1399 |
If optional LOCAL is non-nil, then modify the hook's buffer-local |
|
1400 |
value rather than its global value. This makes the hook local by |
|
1401 |
copying the default value. That copy is then modified. |
|
1402 |
|
|
1403 |
HOOK should be a symbol. If HOOK is void, it is first set to nil. |
|
1404 |
HOOK's value must not be a single hook function. FUNCTION should |
|
1405 |
be a function that takes no arguments and inserts one or multiple |
|
1406 |
sections at point, moving point forward. FUNCTION may choose not |
|
1407 |
to insert its section(s), when doing so would not make sense. It |
|
1408 |
should not be abused for other side-effects. To remove FUNCTION |
|
1409 |
again use `remove-hook'." |
|
1410 |
(unless (boundp hook) |
|
1411 |
(error "Cannot add function to undefined hook variable %s" hook)) |
|
1412 |
(or (default-boundp hook) (set-default hook nil)) |
|
1413 |
(let ((value (if local |
|
1414 |
(if (local-variable-p hook) |
|
1415 |
(symbol-value hook) |
|
1416 |
(unless (local-variable-if-set-p hook) |
|
1417 |
(make-local-variable hook)) |
|
1418 |
(copy-sequence (default-value hook))) |
|
1419 |
(default-value hook)))) |
|
1420 |
(if at |
|
1421 |
(when (setq at (member at value)) |
|
1422 |
(setq value (delq function value)) |
|
1423 |
(cond ((eq append 'replace) |
|
1424 |
(setcar at function)) |
|
1425 |
(append |
|
1426 |
(push function (cdr at))) |
|
1427 |
(t |
|
1428 |
(push (car at) (cdr at)) |
|
1429 |
(setcar at function)))) |
|
1430 |
(setq value (delq function value))) |
|
1431 |
(unless (member function value) |
|
1432 |
(setq value (if append |
|
1433 |
(append value (list function)) |
|
1434 |
(cons function value)))) |
|
1435 |
(when (eq append 'replace) |
|
1436 |
(setq value (delq at value))) |
|
1437 |
(if local |
|
1438 |
(set hook value) |
|
1439 |
(set-default hook value)))) |
|
1440 |
|
|
1441 |
(defun magit-run-section-hook (hook &rest args) |
|
1442 |
"Run HOOK with ARGS, warning about invalid entries." |
|
1443 |
(let ((entries (symbol-value hook))) |
|
1444 |
(unless (listp entries) |
|
1445 |
(setq entries (list entries))) |
|
1446 |
(--when-let (-remove #'functionp entries) |
|
1447 |
(message "`%s' contains entries that are no longer valid. |
|
1448 |
%s\nUsing standard value instead. Please re-configure hook variable." |
|
1449 |
hook |
|
1450 |
(mapconcat (lambda (sym) (format " `%s'" sym)) it "\n")) |
|
1451 |
(sit-for 5) |
|
1452 |
(setq entries (eval (car (get hook 'standard-value))))) |
|
1453 |
(dolist (entry entries) |
|
1454 |
(let ((magit--current-section-hook (cons (list hook entry) |
|
1455 |
magit--current-section-hook))) |
|
1456 |
(apply entry args))))) |
|
1457 |
|
|
1458 |
;;; _ |
|
1459 |
(provide 'magit-section) |
|
1460 |
;;; magit-section.el ends here |