aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuri Linkov2018-12-18 01:11:15 +0200
committerJuri Linkov2018-12-18 01:11:15 +0200
commit6973b1489b24ca4190d24be9e5f887aef2cc9eff (patch)
treee3d85774e1b34de4f41103f84e511adcd5a3264d
parentc5e02f2bce28f3b1f2006ce1f208f4a92ca05ed9 (diff)
downloademacs-6973b1489b24ca4190d24be9e5f887aef2cc9eff.tar.gz
emacs-6973b1489b24ca4190d24be9e5f887aef2cc9eff.zip
Syntactic fontification of diff hunks (bug#33567)
* lisp/vc/diff-mode.el (diff-font-lock-syntax): New defcustom. (diff-default-directory): New buffer-local variable. (diff-indicator-removed, diff-indicator-added) (diff-indicator-changed): Set foreground to distinctive colors. (diff-context): Remove colors to make room for syntax highlighting. (diff-font-lock-keywords): Add diff--font-lock-syntax. (diff--font-lock-cleanup): Remove diff-mode syntax overlays. (diff--font-lock-syntax, diff--font-lock-syntax--refresh) (diff-syntax-fontify-revisions, diff-syntax-fontify-hunk) (diff-syntax-fontify-props): New functions. * lisp/vc/diff.el (diff-no-select): Set diff-default-directory to default-directory. * doc/emacs/files.texi (Diff Mode): Document diff-font-lock-syntax.
-rw-r--r--doc/emacs/files.texi4
-rw-r--r--etc/NEWS6
-rw-r--r--lisp/vc/diff-mode.el243
-rw-r--r--lisp/vc/diff.el3
4 files changed, 249 insertions, 7 deletions
diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi
index b47be51e24c..6e1faf84dcb 100644
--- a/doc/emacs/files.texi
+++ b/doc/emacs/files.texi
@@ -1617,6 +1617,10 @@ displayed in the echo area). With a prefix argument, it tries to
1617modify the original (``old'') source files rather than the patched 1617modify the original (``old'') source files rather than the patched
1618(``new'') source files. 1618(``new'') source files.
1619 1619
1620@vindex diff-font-lock-syntax
1621 If non-@code{nil}, fragments of source in hunks are highlighted
1622according to the appropriate major mode.
1623
1620@node Copying and Naming 1624@node Copying and Naming
1621@section Copying, Naming and Renaming Files 1625@section Copying, Naming and Renaming Files
1622 1626
diff --git a/etc/NEWS b/etc/NEWS
index 95647bbda4f..bc76bec2d75 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -413,6 +413,12 @@ and compares their entire trees.
413*** Hunks are now automatically refined by default. 413*** Hunks are now automatically refined by default.
414To disable it, set the new defcustom 'diff-font-lock-refine' to nil. 414To disable it, set the new defcustom 'diff-font-lock-refine' to nil.
415 415
416+++
417*** Better syntax highlighting of Diff hunks.
418Fragments of source in Diff hunks are now by default highlighted
419according to the appropriate major mode. Customize the new option
420'diff-font-lock-syntax' to nil to disable this.
421
416*** File headers can be shortened, mimicking Magit's diff format. 422*** File headers can be shortened, mimicking Magit's diff format.
417To enable it, set the new defcustom 'diff-font-lock-prettify' to t. 423To enable it, set the new defcustom 'diff-font-lock-prettify' to t.
418 424
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 4adef029847..ed953deb21a 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -56,6 +56,7 @@
56(eval-when-compile (require 'cl-lib)) 56(eval-when-compile (require 'cl-lib))
57 57
58(autoload 'vc-find-revision "vc") 58(autoload 'vc-find-revision "vc")
59(autoload 'vc-find-revision-no-save "vc")
59(defvar vc-find-revision-no-save) 60(defvar vc-find-revision-no-save)
60(defvar add-log-buffer-file-name-function) 61(defvar add-log-buffer-file-name-function)
61 62
@@ -103,12 +104,42 @@ when editing big diffs)."
103 :version "27.1" 104 :version "27.1"
104 :type 'boolean) 105 :type 'boolean)
105 106
107(defcustom diff-font-lock-syntax t
108 "If non-nil, diff hunk font-lock includes source language syntax highlighting.
109This highlighting is the same as added by `font-lock-mode'
110when corresponding source files are visited normally.
111Syntax highlighting is added over diff own highlighted changes.
112
113If t, the default, highlight syntax only in Diff buffers created by Diff
114commands that compare files or by VC commands that compare revisions.
115These provide all necessary context for reliable highlighting. This value
116requires support from a VC backend to find the files being compared.
117For diffs against the working-tree version of a file, the highlighting is
118based on the current file contents. File-based fontification tries to
119infer fontification from the compared files.
120
121If revision-based or file-based method fails, use hunk-based method to get
122fontification from hunk alone if the value is `hunk-also'.
123
124If `hunk-only', fontification is based on hunk alone, without full source.
125It tries to highlight hunks without enough context that sometimes might result
126in wrong fontification. This is the fastest option, but less reliable."
127 :version "27.1"
128 :type '(choice (const :tag "Don't highlight syntax" nil)
129 (const :tag "Hunk-based also" hunk-also)
130 (const :tag "Hunk-based only" hunk-only)
131 (const :tag "Highlight syntax" t)))
132
106(defvar diff-vc-backend nil 133(defvar diff-vc-backend nil
107 "The VC backend that created the current Diff buffer, if any.") 134 "The VC backend that created the current Diff buffer, if any.")
108 135
109(defvar diff-vc-revisions nil 136(defvar diff-vc-revisions nil
110 "The VC revisions compared in the current Diff buffer, if any.") 137 "The VC revisions compared in the current Diff buffer, if any.")
111 138
139(defvar diff-default-directory nil
140 "The default directory where the current Diff buffer was created.")
141(make-variable-buffer-local 'diff-default-directory)
142
112(defvar diff-outline-regexp 143(defvar diff-outline-regexp
113 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)") 144 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
114 145
@@ -295,19 +326,25 @@ well."
295 :version "25.1") 326 :version "25.1")
296 327
297(defface diff-indicator-removed 328(defface diff-indicator-removed
298 '((t :inherit diff-removed)) 329 '((default :inherit diff-removed)
330 (((class color) (min-colors 88))
331 :foreground "#aa2222"))
299 "`diff-mode' face used to highlight indicator of removed lines (-, <)." 332 "`diff-mode' face used to highlight indicator of removed lines (-, <)."
300 :version "22.1") 333 :version "22.1")
301(defvar diff-indicator-removed-face 'diff-indicator-removed) 334(defvar diff-indicator-removed-face 'diff-indicator-removed)
302 335
303(defface diff-indicator-added 336(defface diff-indicator-added
304 '((t :inherit diff-added)) 337 '((default :inherit diff-added)
338 (((class color) (min-colors 88))
339 :foreground "#22aa22"))
305 "`diff-mode' face used to highlight indicator of added lines (+, >)." 340 "`diff-mode' face used to highlight indicator of added lines (+, >)."
306 :version "22.1") 341 :version "22.1")
307(defvar diff-indicator-added-face 'diff-indicator-added) 342(defvar diff-indicator-added-face 'diff-indicator-added)
308 343
309(defface diff-indicator-changed 344(defface diff-indicator-changed
310 '((t :inherit diff-changed)) 345 '((default :inherit diff-changed)
346 (((class color) (min-colors 88))
347 :foreground "#aaaa22"))
311 "`diff-mode' face used to highlight indicator of changed lines." 348 "`diff-mode' face used to highlight indicator of changed lines."
312 :version "22.1") 349 :version "22.1")
313(defvar diff-indicator-changed-face 'diff-indicator-changed) 350(defvar diff-indicator-changed-face 'diff-indicator-changed)
@@ -317,10 +354,7 @@ well."
317 "`diff-mode' face used to highlight function names produced by \"diff -p\".") 354 "`diff-mode' face used to highlight function names produced by \"diff -p\".")
318 355
319(defface diff-context 356(defface diff-context
320 '((((class color grayscale) (min-colors 88) (background light)) 357 '((t nil))
321 :foreground "#333333")
322 (((class color grayscale) (min-colors 88) (background dark))
323 :foreground "#dddddd"))
324 "`diff-mode' face used to highlight context and other side-information." 358 "`diff-mode' face used to highlight context and other side-information."
325 :version "25.1") 359 :version "25.1")
326 360
@@ -406,6 +440,7 @@ and the face `diff-added' for added lines.")
406 (1 font-lock-comment-delimiter-face) 440 (1 font-lock-comment-delimiter-face)
407 (2 font-lock-comment-face)) 441 (2 font-lock-comment-face))
408 ("^[^-=+*!<>#].*\n" (0 'diff-context)) 442 ("^[^-=+*!<>#].*\n" (0 'diff-context))
443 (,#'diff--font-lock-syntax)
409 (,#'diff--font-lock-prettify) 444 (,#'diff--font-lock-prettify)
410 (,#'diff--font-lock-refined))) 445 (,#'diff--font-lock-refined)))
411 446
@@ -1348,6 +1383,7 @@ See `after-change-functions' for the meaning of BEG, END and LEN."
1348 1383
1349(defun diff--font-lock-cleanup () 1384(defun diff--font-lock-cleanup ()
1350 (remove-overlays nil nil 'diff-mode 'fine) 1385 (remove-overlays nil nil 'diff-mode 'fine)
1386 (remove-overlays nil nil 'diff-mode 'syntax)
1351 (when font-lock-mode 1387 (when font-lock-mode
1352 (make-local-variable 'font-lock-extra-managed-props) 1388 (make-local-variable 'font-lock-extra-managed-props)
1353 ;; Added when diff--font-lock-prettify is non-nil! 1389 ;; Added when diff--font-lock-prettify is non-nil!
@@ -2316,6 +2352,199 @@ fixed, visit it in a buffer."
2316 'display ""))))) 2352 'display "")))))
2317 nil) 2353 nil)
2318 2354
2355;;; Syntax highlighting from font-lock
2356
2357(defun diff--font-lock-syntax (max)
2358 "Apply source language syntax highlighting from font-lock.
2359Calls `diff-syntax-fontify' on every hunk found between point
2360and the position in MAX."
2361 (when diff-font-lock-syntax
2362 (when (get-char-property (point) 'diff--font-lock-syntax)
2363 (goto-char (next-single-char-property-change
2364 (point) 'diff--font-lock-syntax nil max)))
2365 (let* ((min (point))
2366 (beg (or (ignore-errors (diff-beginning-of-hunk))
2367 (ignore-errors (diff-hunk-next) (point))
2368 max)))
2369 (while (< beg max)
2370 (let ((end
2371 (save-excursion (goto-char beg) (diff-end-of-hunk) (point))))
2372 (if (< end min) (setq beg min))
2373 (unless (or (< end beg)
2374 (get-char-property beg 'diff--font-lock-syntax))
2375 (diff-syntax-fontify beg end)
2376 (let ((ol (make-overlay beg end)))
2377 (overlay-put ol 'diff--font-lock-syntax t)
2378 (overlay-put ol 'diff-mode 'syntax)
2379 (overlay-put ol 'evaporate t)
2380 (overlay-put ol 'modification-hooks
2381 '(diff--font-lock-syntax--refresh))))
2382 (goto-char (max beg end))
2383 (setq beg (or (ignore-errors (diff-hunk-next) (point)) max))))))
2384 nil)
2385
2386(defun diff--font-lock-syntax--refresh (ol _after _beg _end &optional _len)
2387 (delete-overlay ol))
2388
2389(defun diff-syntax-fontify (beg end)
2390 "Highlight source language syntax in diff hunk between BEG and END."
2391 (save-excursion
2392 (diff-syntax-fontify-hunk beg end t)
2393 (diff-syntax-fontify-hunk beg end nil)))
2394
2395(defvar diff-syntax-fontify-revisions (make-hash-table :test 'equal))
2396
2397(defun diff-syntax-fontify-hunk (beg end old)
2398 "Highlight source language syntax in diff hunk between BEG and END.
2399When OLD is non-nil, highlight the hunk from the old source."
2400 (remove-overlays beg end 'diff-mode 'syntax)
2401 (goto-char beg)
2402 (let* ((hunk (buffer-substring-no-properties beg end))
2403 (text (or (ignore-errors (diff-hunk-text hunk (not old) nil)) ""))
2404 (line (if (looking-at "\\(?:\\*\\{15\\}.*\n\\)?[-@* ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?")
2405 (if old (match-string 1)
2406 (if (match-end 3) (match-string 3) (match-string 1)))))
2407 (line-nb (and line (string-match "\\([0-9]+\\),\\([0-9]+\\)" line)
2408 (list (string-to-number (match-string 1 line))
2409 (string-to-number (match-string 2 line)))))
2410 props)
2411 (cond
2412 ((and diff-vc-backend (not (eq diff-font-lock-syntax 'hunk-only)))
2413 (let* ((file (diff-find-file-name old t))
2414 (revision (and file (if (not old) (nth 1 diff-vc-revisions)
2415 (or (nth 0 diff-vc-revisions)
2416 (vc-working-revision file))))))
2417 (if file
2418 (if (not revision)
2419 ;; Get properties from the current working revision
2420 (when (and (not old) (file-exists-p file) (file-regular-p file))
2421 ;; Try to reuse an existing buffer
2422 (if (get-file-buffer (expand-file-name file))
2423 (with-current-buffer (get-file-buffer (expand-file-name file))
2424 (setq props (diff-syntax-fontify-props nil text line-nb t)))
2425 ;; Get properties from the file
2426 (with-temp-buffer
2427 (insert-file-contents file t)
2428 (setq props (diff-syntax-fontify-props file text line-nb)))))
2429 ;; Get properties from a cached revision
2430 (let* ((buffer-name (format " diff-syntax:%s.~%s~"
2431 (expand-file-name file) revision))
2432 (buffer (gethash buffer-name diff-syntax-fontify-revisions)))
2433 (unless (and buffer (buffer-live-p buffer))
2434 (let* ((vc-buffer (ignore-errors
2435 (vc-find-revision-no-save
2436 (expand-file-name file) revision
2437 diff-vc-backend
2438 (get-buffer-create buffer-name)))))
2439 (when vc-buffer
2440 (setq buffer vc-buffer)
2441 (puthash buffer-name buffer diff-syntax-fontify-revisions))))
2442 (when buffer
2443 (with-current-buffer buffer
2444 (setq props (diff-syntax-fontify-props file text line-nb t))))))
2445 ;; If file is unavailable, get properties from the hunk alone
2446 (setq file (car (diff-hunk-file-names old)))
2447 (with-temp-buffer
2448 (insert text)
2449 (setq props (diff-syntax-fontify-props file text line-nb nil t))))))
2450 ((and diff-default-directory (not (eq diff-font-lock-syntax 'hunk-only)))
2451 (let ((file (car (diff-hunk-file-names old))))
2452 (if (and file (file-exists-p file) (file-regular-p file))
2453 ;; Try to get full text from the file
2454 (with-temp-buffer
2455 (insert-file-contents file t)
2456 (setq props (diff-syntax-fontify-props file text line-nb)))
2457 ;; Otherwise, get properties from the hunk alone
2458 (with-temp-buffer
2459 (insert text)
2460 (setq props (diff-syntax-fontify-props file text line-nb nil t))))))
2461 ((memq diff-font-lock-syntax '(hunk-also hunk-only))
2462 (let ((file (car (diff-hunk-file-names old))))
2463 (with-temp-buffer
2464 (insert text)
2465 (setq props (diff-syntax-fontify-props file text line-nb nil t))))))
2466
2467 ;; Put properties over the hunk text
2468 (goto-char beg)
2469 (when (and props (eq (diff-hunk-style) 'unified))
2470 (while (< (progn (forward-line 1) (point)) end)
2471 (when (or (and (not old) (not (looking-at-p "[-<]")))
2472 (and old (not (looking-at-p "[+>]"))))
2473 (if (and old (not (looking-at-p "[-<]")))
2474 ;; Fontify context lines only from new source,
2475 ;; don't refontify context lines from old source.
2476 (pop props)
2477 (let ((line-props (pop props))
2478 (bol (1+ (point))))
2479 (dolist (prop line-props)
2480 (let ((ol (make-overlay (+ bol (nth 0 prop))
2481 (+ bol (nth 1 prop))
2482 nil 'front-advance nil)))
2483 (overlay-put ol 'evaporate t)
2484 (overlay-put ol 'face (nth 2 prop)))))))))))
2485
2486(defun diff-syntax-fontify-props (file text line-nb &optional no-init hunk-only)
2487 "Get font-lock properties from the source code.
2488FILE is the name of the source file. TEXT is the literal source text from
2489hunk. LINE-NB is a pair of numbers: start line number and the number of
2490lines in the hunk. NO-INIT means no initialization is needed to set major
2491mode. When HUNK-ONLY is non-nil, then don't verify the existence of the
2492hunk text in the source file. Otherwise, don't highlight the hunk if the
2493hunk text is not found in the source file."
2494 (unless no-init
2495 (buffer-disable-undo)
2496 (font-lock-mode -1)
2497 (let ((enable-local-variables :safe) ;; to find `mode:'
2498 (buffer-file-name file))
2499 (set-auto-mode)
2500 (when (and (memq 'generic-mode-find-file-hook find-file-hook)
2501 (fboundp 'generic-mode-find-file-hook))
2502 (generic-mode-find-file-hook))))
2503
2504 (let ((font-lock-defaults (or font-lock-defaults '(nil t)))
2505 (inhibit-read-only t)
2506 props beg end)
2507 (goto-char (point-min))
2508 (if hunk-only
2509 (setq beg (point-min) end (point-max))
2510 (forward-line (1- (nth 0 line-nb)))
2511 ;; non-regexp looking-at to compare hunk text for verification
2512 (if (search-forward text (+ (point) (length text)) t)
2513 (setq beg (- (point) (length text)) end (point))
2514 (goto-char (point-min))
2515 (if (search-forward text nil t)
2516 (setq beg (- (point) (length text)) end (point)))))
2517
2518 (when (and beg end)
2519 (goto-char beg)
2520 (when (text-property-not-all beg end 'fontified t)
2521 (if file
2522 ;; In a temporary or cached buffer
2523 (save-excursion
2524 (font-lock-fontify-region beg end)
2525 (put-text-property beg end 'fontified t))
2526 ;; In an existing buffer
2527 (font-lock-ensure beg end)))
2528
2529 (while (< (point) end)
2530 (let* ((bol (point))
2531 (eol (line-end-position))
2532 line-props
2533 (searching t)
2534 (from (point)) to
2535 (val (get-text-property from 'face)))
2536 (while searching
2537 (setq to (next-single-property-change from 'face nil eol))
2538 (when val (push (list (- from bol) (- to bol) val) line-props))
2539 (setq val (get-text-property to 'face) from to)
2540 (unless (< to eol) (setq searching nil)))
2541 (when val (push (list from eol val) line-props))
2542 (push (nreverse line-props) props))
2543 (forward-line 1)))
2544 (set-buffer-modified-p nil)
2545 (nreverse props)))
2546
2547
2319(defun diff--filter-substring (str) 2548(defun diff--filter-substring (str)
2320 (when diff-font-lock-prettify 2549 (when diff-font-lock-prettify
2321 ;; Strip the `display' properties added by diff-font-lock-prettify, 2550 ;; Strip the `display' properties added by diff-font-lock-prettify,
diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el
index ac94586cace..ed5b49d3bf4 100644
--- a/lisp/vc/diff.el
+++ b/lisp/vc/diff.el
@@ -121,6 +121,8 @@ Possible values are:
121 nil -- no, it does not 121 nil -- no, it does not
122 check -- try to probe whether it does") 122 check -- try to probe whether it does")
123 123
124(defvar diff-default-directory)
125
124(defun diff-no-select (old new &optional switches no-async buf) 126(defun diff-no-select (old new &optional switches no-async buf)
125 ;; Noninteractive helper for creating and reverting diff buffers 127 ;; Noninteractive helper for creating and reverting diff buffers
126 (unless (bufferp new) (setq new (expand-file-name new))) 128 (unless (bufferp new) (setq new (expand-file-name new)))
@@ -165,6 +167,7 @@ Possible values are:
165 (lambda (_ignore-auto _noconfirm) 167 (lambda (_ignore-auto _noconfirm)
166 (diff-no-select old new switches no-async (current-buffer)))) 168 (diff-no-select old new switches no-async (current-buffer))))
167 (setq default-directory thisdir) 169 (setq default-directory thisdir)
170 (setq diff-default-directory default-directory)
168 (let ((inhibit-read-only t)) 171 (let ((inhibit-read-only t))
169 (insert command "\n")) 172 (insert command "\n"))
170 (if (and (not no-async) (fboundp 'make-process)) 173 (if (and (not no-async) (fboundp 'make-process))