[clang-tools-extra] r283306 - Overhaul clang-include-fixer.el

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 5 03:04:13 PDT 2016


Author: hokein
Date: Wed Oct  5 05:04:13 2016
New Revision: 283306

URL: http://llvm.org/viewvc/llvm-project?rev=283306&view=rev
Log:
Overhaul clang-include-fixer.el

General overhaul to fix many coding bugs, simplify the code, and improve readability.

* Clarify documentation strings of user options.
* Say that clang-include-fixer-executable is a file to have auto completion.
* Allow user to select available options for clang-include-fixer-input-format. Turn it into a symbol as it's not a free-form string.
* Remove clang-include-fixer-query-mode. This option was apparently used to select between two different operation modes, which is not a typical use case for user options. Provide two separate commands instead.
* Add a face for the overlay highlighting so that users can customize it.
Move user commands to the front so that readers of the code aren't buried in internal functions.
* Make process calls asynchronous. This is possible here because clang-include-fixer doesn't change files in place. This means input is no longer blocked while clang-include-fixer is running.
* Factor out logic in helper functions to keep functions short.
* Add comments where appropriate.
* Provide an alternative buffer replacement strategy for the case that a single line was inserted (the normal case in the case of clang-include-fixer). This keeps point, markers, and other buffer information intact.
* Use let-alist and association lists instead of property lists to shorten the code.
* Instead of highlighting only the first occurrence of a symbol, highlight all occurrences and move point to the closest one.
* Detect qualified names at point.
* Use filepos-to-bufferpos if available.
* Formatting.

Patch by Philipp Stephani!

Added:
    clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer-test.el
Modified:
    clang-tools-extra/trunk/docs/include-fixer.rst
    clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer.el

Modified: clang-tools-extra/trunk/docs/include-fixer.rst
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/include-fixer.rst?rev=283306&r1=283305&r2=283306&view=diff
==============================================================================
--- clang-tools-extra/trunk/docs/include-fixer.rst (original)
+++ clang-tools-extra/trunk/docs/include-fixer.rst Wed Oct  5 05:04:13 2016
@@ -121,26 +121,19 @@ in your ``.emacs``:
  (add-to-list 'load-path "path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/"
  (require 'clang-include-fixer)
 
-Within Emacs the tool can be invoked with the command ``M-x clang-include-fixer``.
+Within Emacs the tool can be invoked with the command
+``M-x clang-include-fixer``.  This will insert the header that defines the
+first undefined symbol; if there is more than one header that would define the
+symbol, the user is prompted to select one.
 
-Make sure Emacs can find :program:`clang-include-fixer`:
-
-- Add the path to :program:`clang-include-fixer` to the PATH environment variable.
-
-Customized settings in `.emacs`:
-
-- ``(custom-set-variables '(clang-include-fixer-executable "/path/to/include-fixer"))``
+To include the header that defines the symbol at point, run
+``M-x clang-include-fixer-at-point``.
 
-  Set clang-include-fixer binary file path.
-
-- ``(custom-set-variables '(clang-include-fixer-query-mode t))``
-
-  Set to `t` if you want to insert ``#include`` for the symbol under the cursor.
-  Default is `nil`. Compared to normal mode, this mode won't parse the source
-  file and only search the sysmbol from database, which is faster than normal
-  mode.
+Make sure Emacs can find :program:`clang-include-fixer`:
 
-See ``clang-include-fixer.el`` for more details.
+- Either add the parent directory of :program:`clang-include-fixer` to the PATH
+  environment variable, or customize the Emacs user option
+  ``clang-include-fixer-executable`` to point to the file name of the program.
 
 How it Works
 ============

Added: clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer-test.el
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer-test.el?rev=283306&view=auto
==============================================================================
--- clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer-test.el (added)
+++ clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer-test.el Wed Oct  5 05:04:13 2016
@@ -0,0 +1,40 @@
+;;; clang-include-fixer-test.el --- unit tests for clang-include-fixer.el  -*- lexical-binding: t; -*-
+
+;;; Commentary:
+
+;; Unit tests for clang-include-fixer.el.
+
+;;; Code:
+
+(require 'cc-mode)
+(require 'ert)
+
+(ert-deftest clang-include-fixer--insert-line ()
+  "Unit test for `clang-include-fixer--insert-line'."
+  (with-temp-buffer
+    (insert "aa\nab\nac\nad\n")
+    (let ((from (current-buffer)))
+      (with-temp-buffer
+        (insert "aa\nac\nad\n")
+        (let ((to (current-buffer)))
+          (should (clang-include-fixer--insert-line from to))
+          (should (equal (buffer-string) "aa\nab\nac\nad\n")))))
+    (should (equal (buffer-string) "aa\nab\nac\nad\n"))))
+
+(ert-deftest clang-include-fixer--symbol-at-point ()
+  "Unit test for `clang-include-fixer--symbol-at-point'."
+  (with-temp-buffer
+    (insert "a+bbb::cc")
+    (c++-mode)
+    (goto-char (point-min))
+    (should (equal (clang-include-fixer--symbol-at-point) "a"))
+    (forward-char)
+    ;; Emacs treats the character immediately following a symbol as part of the
+    ;; symbol.
+    (should (equal (clang-include-fixer--symbol-at-point) "a"))
+    (forward-char)
+    (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc"))
+    (goto-char (point-max))
+    (should (equal (clang-include-fixer--symbol-at-point) "bbb::cc"))))
+
+;;; clang-include-fixer-test.el ends here

Modified: clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer.el
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer.el?rev=283306&r1=283305&r2=283306&view=diff
==============================================================================
--- clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer.el (original)
+++ clang-tools-extra/trunk/include-fixer/tool/clang-include-fixer.el Wed Oct  5 05:04:13 2016
@@ -1,7 +1,7 @@
-;;; clang-include-fxier.el --- Emacs integration of the clang include fixer
+;;; clang-include-fixer.el --- Emacs integration of the clang include fixer  -*- lexical-binding: t; -*-
 
 ;; Keywords: tools, c
-;; Package-Requires: ((json "1.2"))
+;; Package-Requires: ((cl-lib "0.5") (json "1.2") (let-alist "1.0.4"))
 
 ;;; Commentary:
 
@@ -12,219 +12,395 @@
 
 ;;; Code:
 
+(require 'cl-lib)
 (require 'json)
+(require 'let-alist)
 
 (defgroup clang-include-fixer nil
-  "Include fixer."
+  "Clang-based include fixer."
   :group 'tools)
 
 (defcustom clang-include-fixer-executable
   "clang-include-fixer"
-  "Location of the `clang-include-fixer' executable.
+  "Location of the clang-include-fixer executable.
 
-   A string containing the name or the full path of the executable."
+A string containing the name or the full path of the executable."
   :group 'clang-include-fixer
-  :type 'string
+  :type '(file :must-match t)
   :risky t)
 
 (defcustom clang-include-fixer-input-format
-  "yaml"
-  "clang-include-fixer input format."
+  'yaml
+  "Input format for clang-include-fixer.
+This string is passed as -db argument to
+`clang-include-fixer-executable'."
   :group 'clang-include-fixer
-  :type 'string
+  :type '(radio
+          (const :tag "Hard-coded mapping" :fixed)
+          (const :tag "YAML" yaml)
+          (symbol :tag "Other"))
   :risky t)
 
 (defcustom clang-include-fixer-init-string
   ""
-  "clang-include-fixer init string."
+  "Database initialization string for clang-include-fixer.
+This string is passed as -input argument to
+`clang-include-fixer-executable'."
   :group 'clang-include-fixer
   :type 'string
   :risky t)
 
-(defcustom clang-include-fixer-query-mode
-  nil
-  "clang-include-fixer query mode."
-  :group 'clang-include-fixer
-  :type 'boolean
-  :risky t)
-
-(defun clang-include-fixer-call-executable (callee
-                                            include-fixer-parameter-a
-                                            &optional include-fixer-parameter-b
-                                            &optional include-fixer-parameter-c
-                                            )
-  "Calls clang-include-fixer with parameters INCLUDE-FIXER-PARAMETER-[ABC].
-   If the call was successful the returned result is stored in a temp buffer
-   and the function CALLEE is called on this temp buffer."
-
-    (let ((temp-buffer (generate-new-buffer " *clang-include-fixer-temp*"))
-          (temp-file (make-temp-file "clang-include-fixer")))
-      (unwind-protect
-          (let (status stderr operations)
-            (if (eq include-fixer-parameter-c nil)
-                (setq status
-                      (call-process-region
-                       (point-min) (point-max) clang-include-fixer-executable
-                       nil `(,temp-buffer ,temp-file) nil
-
-                       "-stdin"
-                       include-fixer-parameter-a
-                       (buffer-file-name)
-                       ))
-              (setq status
-                    (call-process-region
-                     (point-min) (point-max) clang-include-fixer-executable
-                     nil `(,temp-buffer ,temp-file) nil
-
-                     "-stdin"
-                     include-fixer-parameter-a
-                     include-fixer-parameter-b
-                     include-fixer-parameter-c
-                     (buffer-file-name)
-                     )))
-
-            (setq stderr
-                  (with-temp-buffer
-                    (insert-file-contents temp-file)
-                    (when (> (point-max) (point-min))
-                      (insert ": "))
-                    (buffer-substring-no-properties
-                     (point-min) (line-end-position))))
-
-            (cond
-             ((stringp status)
-              (error "(clang-include-fixer killed by signal %s%s)" status
-                     stderr))
-             ((not (equal 0 status))
-              (error "(clang-include-fixer failed with code %d%s)" status
-                     stderr)))
-            (funcall callee temp-buffer))
-        (delete-file temp-file)
-        (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
-
-
-(defun clang-include-fixer-replace_buffer (temp-buffer)
-  "Replace current buffer by content of TEMP-BUFFER"
-
-  (with-current-buffer temp-buffer
-    (setq temp-start (point-min))
-    (setq temp-end (point-max))
-    )
-  (barf-if-buffer-read-only)
-  (erase-buffer)
-  (save-excursion
-    (insert-buffer-substring temp-buffer temp-start temp-end)))
-
-
-(defun clang-include-fixer-add-header (temp-buffer)
-  "Analyse the result of include-fixer stored in TEMP_BUFFER and add a
-   missing header if there is any. If there are multiple possible headers
-   the user can select one of them to be included."
-
-  (with-current-buffer temp-buffer
-    (setq result (buffer-substring (point-min) (point-max)))
-    (setq include-fixer-context
-          (let ((json-object-type 'plist))
-            (json-read-from-string result))))
-
-  ;; The header-infos is already sorted by include-fixer.
-  (setq header-infos (plist-get include-fixer-context :HeaderInfos))
-  (setq query-symbol-infos (plist-get include-fixer-context :QuerySymbolInfos))
-
-  (if (eq 0 (length query-symbol-infos))
-      (message "The file is fine, no need to add a header.")
-
-    (setq symbol-info (elt query-symbol-infos 0))
-    (setq symbol (plist-get symbol-info :RawIdentifier))
-    (setq symbol-offset (plist-get (plist-get symbol-info :Range)
-                                   :Offset))
-
-    ;; Check the number of choices
-    (if (eq 0 (length header-infos))
-	(progn
-	  (goto-char (1+ symbol-offset))
-	  (message (concat "Couldn't find header for '" symbol "'.")))
-
-      (setq symbol-length (plist-get (plist-get symbol-info :Range)
-				     :Length))
-      (goto-char (1+ symbol-offset))
-      (setq symbol-overlay (make-overlay (1+ symbol-offset)
-					 (+ symbol-offset symbol-length +1)))
-      (overlay-put symbol-overlay 'face '(:background "green" :foreground
-						      "black"))
-
-      (message (number-to-string symbol-offset))
-      (message (number-to-string symbol-length))
-
-      (if (eq 1 (length header-infos))
-	  (progn
-	    (setq missing-header
-		  (plist-get (elt header-infos 0) :Header))
-	    (message (concat "Only one include is missing: "
-			     missing-header )))
-
-	;; Now iterate over vector and add items to list
-	(setq include-list '())
-	(setq index 0)
-	(while (< index (length header-infos))
-	  (setq entry (elt header-infos index))
-	  (add-to-list 'include-list  (plist-get entry :Header))
-	  (setq index (1+ index))
-	  )
-
-	(setq option-message (concat "Select include for '"
-				     symbol
-				     "' :"))
-	(setq missing-header (ido-completing-read
-                              option-message include-list)))
-
-      ;; Now select set correct header info.
-      (setq header-plist '())
-      (setq index 0)
-      (while (< index (length header-infos))
-	(setq entry (elt header-infos index))
-	(setq index (1+ index))
-	(if (eq (plist-get entry :Header) missing-header)
-	    (setq header-plist entry)))
-      (setq include-fixer-context (plist-put
-				   include-fixer-context
-				   ':HeaderInfos (vector header-plist)))
-
-      (clang-include-fixer-call-executable
-       'clang-include-fixer-replace_buffer
-       (concat "-insert-header=" (json-encode include-fixer-context)))
-      (delete-overlay symbol-overlay))))
-
+(defface clang-include-fixer-highlight '((t :background "green"))
+  "Used for highlighting the symbol for which a header file is being added.")
 
 (defun clang-include-fixer ()
-  "Invokes the Include Fixer to insert missing C++ headers."
+  "Invoke the Include Fixer to insert missing C++ headers."
   (interactive)
-
   (message (concat "Calling the include fixer. "
-           "This might take some seconds. Please wait."))
+                   "This might take some seconds. Please wait."))
+  (clang-include-fixer--start #'clang-include-fixer--add-header
+                              "-output-headers"))
 
-  (if clang-include-fixer-query-mode
-      (let (p1 p2)
-      (save-excursion
-        (skip-chars-backward "-a-zA-Z0-9_:")
-        (setq p1 (point))
-        (skip-chars-forward "-a-zA-Z0-9_:")
-        (setq p2 (point))
-        (setq query-symbol (buffer-substring-no-properties p1 p2))
-        (if (string= "" query-symbol)
-            (message "Skip querying empty symbol.")
-          (clang-include-fixer-call-executable
-            'clang-include-fixer-add-header
-            (concat "-db=" clang-include-fixer-input-format)
-            (concat "-input=" clang-include-fixer-init-string)
-            (concat "-query-symbol=" (thing-at-point 'symbol))
-          ))))
-    (clang-include-fixer-call-executable
-      'clang-include-fixer-add-header
-      (concat "-db=" clang-include-fixer-input-format)
-      (concat "-input=" clang-include-fixer-init-string)
-      "-output-headers"))
-  )
+(defun clang-include-fixer-at-point ()
+  "Invoke the Clang include fixer for the symbol at point."
+  (interactive)
+  (let ((symbol (clang-include-fixer--symbol-at-point)))
+    (unless symbol
+      (user-error "No symbol at current location"))
+    (clang-include-fixer--start #'clang-include-fixer--add-header
+                                (format "-query-symbol=%s" symbol))))
+
+(defun clang-include-fixer--start (callback &rest args)
+  "Asynchronously start clang-include-fixer with parameters ARGS.
+The current file name is passed after ARGS as last argument.  If
+the call was successful the returned result is stored in a
+temporary buffer, and CALLBACK is called with the temporary
+buffer as only argument."
+  (let ((process (if (fboundp 'make-process)
+                     ;; Prefer using ‘make-process’ if available, because
+                     ;; ‘start-process’ doesn’t allow us to separate the
+                     ;; standard error from the output.
+                     (clang-include-fixer--make-process callback args)
+                   (clang-include-fixer--start-process callback args))))
+    (save-restriction
+      (widen)
+      (process-send-region process (point-min) (point-max)))
+    (process-send-eof process))
+  nil)
+
+(defun clang-include-fixer--make-process (callback args)
+  "Start a new clang-incude-fixer process using `make-process'.
+CALLBACK is called after the process finishes successfully; it is
+called with a single argument, the buffer where standard output
+has been inserted.  ARGS is a list of additional command line
+arguments.  Return the new process object."
+  (let* ((stdin (current-buffer))
+         (stdout (generate-new-buffer "*clang-include-fixer output*"))
+         (stderr (generate-new-buffer "*clang-include-fixer errors*")))
+    (make-process :name "clang-include-fixer"
+                  :buffer stdout
+                  :command (clang-include-fixer--command args)
+                  :coding 'utf-8-unix
+                  :connection-type 'pipe
+                  :sentinel (clang-include-fixer--sentinel stdin stdout stderr
+                                                           callback)
+                  :stderr stderr)))
+
+(defun clang-include-fixer--start-process (callback args)
+  "Start a new clang-incude-fixer process using `start-process'.
+CALLBACK is called after the process finishes successfully; it is
+called with a single argument, the buffer where standard output
+has been inserted.  ARGS is a list of additional command line
+arguments.  Return the new process object."
+  (let* ((stdin (current-buffer))
+         (stdout (generate-new-buffer "*clang-include-fixer output*"))
+         (process-connection-type nil)
+         (process (apply #'start-process "clang-include-fixer" stdout
+                         (clang-include-fixer--command args))))
+    (set-process-coding-system process 'utf-8-unix 'utf-8-unix)
+    (set-process-sentinel process
+                          (clang-include-fixer--sentinel stdin stdout nil
+                                                         callback))
+    process))
+
+(defun clang-include-fixer--command (args)
+  "Return the clang-include-fixer command line.
+Returns a list; the first element is the binary to
+execute (`clang-include-fixer-executable'), and the remaining
+elements are the command line arguments.  Adds proper arguments
+for `clang-include-fixer-input-format' and
+`clang-include-fixer-init-string'.  Appends the current buffer's
+file name; prepends ARGS directly in front of it."
+  (cl-check-type args list)
+  `(,clang-include-fixer-executable
+    ,(format "-db=%s" clang-include-fixer-input-format)
+    ,(format "-input=%s" clang-include-fixer-init-string)
+    "-stdin"
+    , at args
+    ,(buffer-file-name)))
+
+(defun clang-include-fixer--sentinel (stdin stdout stderr callback)
+  "Return a process sentinel for clang-include-fixer processes.
+STDIN, STDOUT, and STDERR are buffers for the standard streams;
+only STDERR may be nil.  CALLBACK is called in the case of
+success; it is called with a single argument, STDOUT.  On
+failure, a buffer containing the error output is displayed."
+  (cl-check-type stdin buffer-live)
+  (cl-check-type stdout buffer-live)
+  (cl-check-type stderr (or null buffer-live))
+  (cl-check-type callback function)
+  (lambda (process event)
+    (cl-check-type process process)
+    (cl-check-type event string)
+    (unwind-protect
+        (if (string-equal event "finished\n")
+            (progn
+              (when stderr (kill-buffer stderr))
+              (with-current-buffer stdin
+                (funcall callback stdout))
+              (kill-buffer stdout))
+          (when stderr (kill-buffer stdout))
+          (message "clang-include-fixer failed")
+          (with-current-buffer (or stderr stdout)
+            (insert "\nProcess " (process-name process)
+                    ?\s event))
+          (display-buffer (or stderr stdout))))
+    nil))
+
+(defun clang-include-fixer--replace-buffer (stdout)
+  "Replace current buffer by content of STDOUT."
+  (cl-check-type stdout buffer-live)
+  (barf-if-buffer-read-only)
+  (unless (clang-include-fixer--insert-line stdout (current-buffer))
+    (erase-buffer)
+    (insert-buffer-substring stdout))
+  (message "Fix applied")
+  nil)
+
+(defun clang-include-fixer--insert-line (from to)
+  "Insert a single missing line from the buffer FROM into TO.
+FROM and TO must be buffers.  If the contents of FROM and TO are
+equal, do nothing and return non-nil.  If FROM contains a single
+line missing from TO, insert that line into TO so that the buffer
+contents are equal and return non-nil.  Otherwise, do nothing and
+return nil.  Buffer restrictions are ignored."
+  (cl-check-type from buffer-live)
+  (cl-check-type to buffer-live)
+  (with-current-buffer from
+    (save-excursion
+      (save-restriction
+        (widen)
+        (with-current-buffer to
+          (save-excursion
+            (save-restriction
+              (widen)
+              ;; Search for the first buffer difference.
+              (let ((chars (abs (compare-buffer-substrings to nil nil from nil nil))))
+                (if (zerop chars)
+                    ;; Buffer contents are equal, nothing to do.
+                    t
+                  (goto-char (point-min))
+                  (forward-char chars)
+                  ;; We might have ended up in the middle of a line if the
+                  ;; current line partially matches.  In this case we would
+                  ;; have to insert more than a line.  Move to the beginning of
+                  ;; the line to avoid this situation.
+                  (beginning-of-line)
+                  (with-current-buffer from
+                    (goto-char (point-min))
+                    (forward-char chars)
+                    (beginning-of-line)
+                    (let ((from-begin (point))
+                          (from-end (progn (forward-line) (point)))
+                          (to-point (with-current-buffer to (point))))
+                      ;; Search for another buffer difference after the line in
+                      ;; question.  If there is none, we can proceed.
+                      (when (zerop (compare-buffer-substrings from from-end nil
+                                                              to to-point nil))
+                        (with-current-buffer to
+                          (insert-buffer-substring from from-begin from-end))
+                        t))))))))))))
+
+(defun clang-include-fixer--add-header (stdout)
+  "Analyse the result of include-fixer stored in STDOUT.
+Add a missing header if there is any.  If there are multiple
+possible headers the user can select one of them to be included.
+Temporarily highlight the affected symbols.  Asynchronously call
+clang-include-fixer to insert the selected header."
+  (cl-check-type stdout buffer-live)
+  (let ((context (clang-include-fixer--parse-json stdout)))
+    (let-alist context
+      (cond
+       ((null .QuerySymbolInfos)
+        (message "The file is fine, no need to add a header."))
+       ((null .HeaderInfos)
+        (message "Couldn't find header for '%s'"
+                 (let-alist (car .QuerySymbolInfos) .RawIdentifier)))
+       (t
+        ;; Replace the HeaderInfos list by a single header selected by
+        ;; the user.
+        (clang-include-fixer--select-header context)
+        ;; Call clang-include-fixer again to insert the selected header.
+        (clang-include-fixer--start
+         #'clang-include-fixer--replace-buffer
+         (format "-insert-header=%s"
+                 (clang-include-fixer--encode-json context)))))))
+  nil)
+
+(defun clang-include-fixer--select-header (context)
+  "Prompt the user for a header if necessary.
+CONTEXT must be a clang-include-fixer context object in
+association list format.  If it contains more than one HeaderInfo
+element, prompt the user to select one of the headers.  CONTEXT
+is modified to include only the selected element."
+  (cl-check-type context cons)
+  (let-alist context
+    (if (cdr .HeaderInfos)
+        (clang-include-fixer--prompt-for-header context)
+      (message "Only one include is missing: %s"
+               (let-alist (car .HeaderInfos) .Header))))
+  nil)
+
+(defvar clang-include-fixer--history nil
+  "History for `clang-include-fixer--prompt-for-header'.")
+
+(defun clang-include-fixer--prompt-for-header (context)
+  "Prompt the user for a single header.
+The choices are taken from the HeaderInfo elements in CONTEXT.
+They are replaced by the single element selected by the user."
+  (let-alist context
+    (let ((symbol (clang-include-fixer--symbol-name .QuerySymbolInfos))
+          ;; Add temporary highlighting so that the user knows which
+          ;; symbols the current session is about.
+          (overlays (mapcar #'clang-include-fixer--highlight .QuerySymbolInfos)))
+      (unwind-protect
+          (save-excursion
+            ;; While prompting, go to the closest overlay so that the user sees
+            ;; some context.
+            (goto-char (clang-include-fixer--closest-overlay overlays))
+            (cl-flet ((header (info) (let-alist info .Header)))
+              ;; The header-infos is already sorted by include-fixer.
+              (let* ((header (ido-completing-read
+                              (format-message "Select include for '%s': "
+                                              symbol)
+                              (mapcar #'header .HeaderInfos)
+                              nil :require-match nil
+                              'clang-include-fixer--history))
+                     (info (cl-find header .HeaderInfos :key #'header)))
+                (cl-assert info)
+                (setcar .HeaderInfos info)
+                (setcdr .HeaderInfos nil))))
+        (mapc #'delete-overlay overlays)))))
+
+(defun clang-include-fixer--symbol-name (symbol-infos)
+  "Return the unique symbol name in SYMBOL-INFOS.
+Raise a signal if the symbol name is not unique."
+  (let ((symbols (delete-dups (mapcar (lambda (info)
+                                        (let-alist info .RawIdentifier))
+                                      symbol-infos))))
+    (when (cdr symbols)
+      (error "Multiple symbols %s returned" symbols))
+    (car symbols)))
+
+(defun clang-include-fixer--highlight (symbol-info)
+  "Add an overlay to highlight SYMBOL-INFO.
+Return the overlay object."
+  (let ((overlay (let-alist symbol-info
+                   (make-overlay
+                    (clang-include-fixer--filepos-to-bufferpos
+                     .Range.Offset 'approximate)
+                    (clang-include-fixer--filepos-to-bufferpos
+                     (+ .Range.Offset .Range.Length) 'approximate)))))
+    (overlay-put overlay 'face 'clang-include-fixer-highlight)
+    overlay))
+
+(defun clang-include-fixer--closest-overlay (overlays)
+  "Return the start of the overlay in OVERLAYS that is closest to point."
+  (cl-check-type overlays cons)
+  (let ((point (point))
+        acc)
+    (dolist (overlay overlays acc)
+      (let ((start (overlay-start overlay)))
+        (when (or (null acc) (< (abs (- point start)) (abs (- point acc))))
+          (setq acc start))))))
+
+(defun clang-include-fixer--parse-json (buffer)
+  "Parse a JSON response from clang-include-fixer in BUFFER.
+Return the JSON object as an association list."
+  (with-current-buffer buffer
+    (save-excursion
+      (goto-char (point-min))
+      (let ((json-object-type 'alist)
+            (json-array-type 'list)
+            (json-key-type 'symbol)
+            (json-false :json-false)
+            (json-null nil)
+            (json-pre-element-read-function nil)
+            (json-post-element-read-function nil))
+        (json-read)))))
+
+(defun clang-include-fixer--encode-json (object)
+  "Return the JSON representation of OBJECT as a string."
+  (let ((json-encoding-separator ",")
+        (json-encoding-default-indentation "  ")
+        (json-encoding-pretty-print nil)
+        (json-encoding-lisp-style-closings nil)
+        (json-encoding-object-sort-predicate nil))
+    (json-encode object)))
+
+(defun clang-include-fixer--symbol-at-point ()
+  "Return the qualified symbol at point.
+If there is no symbol at point, return nil."
+  ;; Let ‘bounds-of-thing-at-point’ to do the hard work and deal with edge
+  ;; cases.
+  (let ((bounds (bounds-of-thing-at-point 'symbol)))
+    (when bounds
+      (let ((beg (car bounds))
+            (end (cdr bounds)))
+        (save-excursion
+          ;; Extend the symbol range to the left.  Skip over namespace
+          ;; delimiters and parent namespace names.
+          (goto-char beg)
+          (while (and (clang-include-fixer--skip-double-colon-backward)
+                      (skip-syntax-backward "w_")))
+          ;; Skip over one more namespace delimiter, for absolute names.
+          (clang-include-fixer--skip-double-colon-backward)
+          (setq beg (point))
+          ;; Extend the symbol range to the right.  Skip over namespace
+          ;; delimiters and child namespace names.
+          (goto-char end)
+          (while (and (clang-include-fixer--skip-double-colon-forward)
+                      (skip-syntax-forward "w_")))
+          (setq end (point)))
+        (buffer-substring-no-properties beg end)))))
+
+(defun clang-include-fixer--skip-double-colon-forward ()
+  "Skip a double colon.
+When the next two characters are '::', skip them and return
+non-nil.  Otherwise return nil."
+  (let ((end (+ (point) 2)))
+    (when (and (<= end (point-max))
+               (string-equal (buffer-substring-no-properties (point) end) "::"))
+      (goto-char end)
+      t)))
+
+(defun clang-include-fixer--skip-double-colon-backward ()
+  "Skip a double colon.
+When the previous two characters are '::', skip them and return
+non-nil.  Otherwise return nil."
+  (let ((beg (- (point) 2)))
+    (when (and (>= beg (point-min))
+               (string-equal (buffer-substring-no-properties beg (point)) "::"))
+      (goto-char beg)
+      t)))
+
+;; ‘filepos-to-bufferpos’ is new in Emacs 25.1.  Provide a fallback for older
+;; versions.
+(defalias 'clang-include-fixer--filepos-to-bufferpos
+  (if (fboundp 'filepos-to-bufferpos)
+      'filepos-to-bufferpos
+    (lambda (byte &optional _quality _coding-system)
+      (byte-to-position (1+ byte)))))
 
 (provide 'clang-include-fixer)
 ;;; clang-include-fixer.el ends here




More information about the cfe-commits mailing list