                 "→ decidable (eq zero (succ m'))")))
             '(121 2)))
 
-;; Infolist Parsing
+;; Info Parsing
 ;; ================
-(defun lean-infolist-split-lines (lines)
-  "Split lines into list of list of strings
-
-Use \"-- ACK\" as a delim and stop processing when it encounters \"-- ENDINFO\""
-  (lean-split-seq-if
-   (split-string lines "\n")
-   'lean-info-ack-str-p
-   '(lambda (x) (not (lean-info-endinfo-str-p x)))))
-(defun lean-infolist-filter-seq (seq)
-  "Filter warning and error messages"
-  (cl-loop for line in seq
-           when (and (not (search "warning:" line))
-                     (not (search "error:" line)))
-           collect line))
-
-(defun lean-infolist-parse (lines)
-  "Parse lines into info-list"
-  (let ((string-seq-seq (lean-infolist-filter-seq (lean-infolist-split-lines lines))))
+(defun lean-info-list-split (str)
+  "Parse string into list of list of strings.
+
+Take out \"BEGININFO\" and \"ENDINFO\" and Use \"ACK\" as a delim."
+  (-split-on "-- ACK"
+             (--filter (not (or (string= "-- BEGININFO" it)
+                                (string= "-- ENDINFO" it)))
+                       (split-string str "\n"))))
+
+(defun lean-info-list-parse-string (str)
+  "Parse string into info-list"
+  (let ((string-seq-seq (lean-info-list-split str))
+        result)
     (cl-loop for string-seq in string-seq-seq
              when string-seq
-             collect (cond ((lean-typeinfo-str-seq-p string-seq)
-                            (lean-typeinfo-parse string-seq))
-                           ((lean-overload-str-seq-p string-seq)
-                            (lean-overload-parse string-seq))
-                           ((lean-synth-str-seq-p string-seq)
-                            (lean-synth-parse string-seq))
-                           ((lean-identifier-str-seq-p string-seq)
-                            (lean-identifier-parse string-seq))
-                           ((lean-symbol-str-seq-p string-seq)
-                            (lean-symbol-parse string-seq))
-                           ((lean-nay-str-seq-p string-seq)
-                            (lean-nay-parse string-seq))))))
-
-;; -- test
-(cl-assert
- (equal (lean-infolist-parse
-         (concat "-- TYPE|15|38" "\n"
-                 "num" "\n"
-                 "-- ACK" "\n"
-                 "-- TYPE|15|40" "\n"
-                 "num → num → Prop" "\n"
-                 "-- ACK" "\n"
-                 "-- OVERLOAD|15|42" "\n"
-                 "f" "\n"
-                 "--" "\n"
-                 "foo.f" "\n"
-                 "-- ACK" "\n"
-                 "-- TYPE|15|42" "\n"
-                 "num → num" "\n"
-                 "-- ACK" "\n"
-                 "-- TYPE|15|44" "\n"
-                 "num" "\n"
-                 "-- ACK" "\n"
-                 "-- ENDINFO" "\n"))
-        '((TYPE (15 38) ("num"))
-          (TYPE (15 40) ("num → num → Prop"))
-          (OVERLOAD (15 42) (("f")
-                             ("foo.f")))
-          (TYPE (15 42) ("num → num"))
-          (TYPE (15 44) ("num")))))
-(cl-assert
- (equal
-  (lean-infolist-parse
-   (concat
-    "-- TYPE|121|2\nnot (eq zero (succ m')) → decidable (eq zero (succ m'))" "\n"
-    "-- ACK" "\n"
-    "-- TYPE|121|7\nne (succ m') zero → ne zero (succ m')" "\n"
-    "-- ACK" "\n"
-    "-- TYPE|121|16" "\n"
-    "∀ (n : nat), ne (succ n) zero" "\n"
-    "-- ACK" "\n"
-    "-- TYPE|121|29" "\n"
-    "nat" "\n"
-    "-- ACK" "\n"
-    "-- OVERLOAD|121|2" "\n"
-    "not (eq zero (succ m'))" "\n"
-    "→ decidable (eq zero (succ m'))" "\n"
-    "--" "\n"
-    "not (eq one (succ m'))" "\n"
-    "→ decidable (eq one (succ m'))" "\n"
-    "--" "\n"
-    "not (eq two (succ m'))" "\n"
-    "→ decidable (eq two (succ m'))" "\n"
-    "-- ENDINFO" "\n"))
-  '((TYPE (121 2) ("not (eq zero (succ m')) → decidable (eq zero (succ m'))"))
-    (TYPE (121 7) ("ne (succ m') zero → ne zero (succ m')"))
-    (TYPE (121 16) ("∀ (n : nat), ne (succ n) zero"))
-    (TYPE (121 29) ("nat"))
-    (OVERLOAD (121 2) (("not (eq zero (succ m'))" "→ decidable (eq zero (succ m'))")
-                       ("not (eq one (succ m'))" "→ decidable (eq one (succ m'))")
-                       ("not (eq two (succ m'))" "→ decidable (eq two (succ m'))"))))))
+             do (setq result
+                      (or (lean-info-type-parse string-seq)
+                          (lean-info-overload-parse string-seq)
+                          (lean-info-synth-parse string-seq)
+                          (lean-info-identifier-parse string-seq)
+                          (lean-info-symbol-parse string-seq)
+                          (lean-info-nay-parse string-seq)))
+             when result
+             collect result)))
+
+(defun lean-info-list-filter (info-list start-column)
+  (--filter (let ((col (lean-info-column it)))
+              (and col (= start-column col)))
+            info-list))
+
+(defun lean-get-partial-names (full-name)
+  "Given a full-name \"a.b.c.d\", return a set of partial names (\"a.b.c.d\" \"b.c.d\" \"c.d\" \"d\")"
+  (cl-labels ((helper(l1 l2 names)
+                  (cond (l1 (helper
+                             (-butlast l1)
+                             (cons nil (-butlast l2))
+                             (-zip-with (lambda (x y) (if y (concat x "." y) x))
+                                        names
+                                        (cons nil (-butlast l2)))))
+                        (t (reverse names)))))
+    (let ((items (reverse (split-string full-name "\\."))))
+      (helper items items items))))
+
+(defun lean-match-name-at-pos (file-name line-number column-number name)
+  "Return t if there is name at pos in a file."
+  ;; Try to use a existing buffer if there is one
+  (let ((buffer (flymake-find-buffer-for-file file-name))
+        str pos)
+    (unless buffer
+      ;; In case a user haven't opened the file before, we read the
+      ;; file to the temp buffer (*lean-server-temp*) and proceed.
+      (setq buffer (get-buffer-create "*lean-server-temp*"))
+      (with-current-buffer buffer
+        (erase-buffer)
+        (insert-file-contents file-name)))
+    (with-current-buffer buffer
+      (save-excursion
+        (goto-char (point-min))
+        (forward-line (1- line-number))
+        (forward-char column-number)
+        (setq pos (point))
+        (setq str (buffer-substring-no-properties pos (+ pos (length name))))
+        (string= name str)))))
+
+(defun lean-match-full-name-at-pos (file-name line-number column-number full-name)
+  "Return the matched name for the given full-name if any."
+  (let ((partial-names (lean-get-partial-names full-name)))
+    (--first (lean-match-name-at-pos file-name line-number column-number it) partial-names)))
+
+(defun lean-info-list-find-start-column (info-list file-name column-number)
+  "Find the start-column of the id/symbol in info-list at a file-name/column-number"
+
+  ;; Extract symbol, ids
+  (let* ((sorted-id-symbol-list
+          (-sort (-on '< 'lean-info-column)
+                 (--filter (or (lean-info-identifier-p it)
+                               (lean-info-symbol-p it))
+                           info-list)))
+         (candidate
+          (--last (<= (lean-info-column it) column-number)
+                  sorted-id-symbol-list))
+         matched-name
+         start-column
+         full-name)
+    (when candidate
+      (setq start-column (lean-info-column candidate))
+      (setq full-name    (lean-info-id-symbol-body-str candidate))
+      (setq matched-name (lean-match-full-name-at-pos
+                          file-name
+                          (lean-info-line-number candidate)
+                          start-column
+                          full-name))
+      (when (< column-number
+               (+ start-column (length matched-name)))
+        start-column))))
+
+(defun lean-info-list-parse (str &optional file-name column-number)
+  (let ((info-list (lean-info-list-parse-string str))
+        start-column)
+    (cond
+     ;; When file-name/column-number is specified, try to start-column of id/symbol
+     ((and file-name column-number)
+      (setq start-column (lean-info-list-find-start-column info-list file-name column-number))
+      (if start-column
+          (lean-info-list-filter info-list start-column)
+        ;; If there is no symbol at column-number, return nil
+        nil))
+     ;; When not specified, just return info-list.
+     (t info-list))))
+
+(cl-defstruct lean-info-record type overload synth identifier symbol nay)
+
+(defun lean-info-record-parse (string &optional file-name column-number)
+  "Parse string into info-record"
+  (let* ((info-list   (lean-info-list-parse string file-name column-number))
+         (types       (-filter 'lean-info-type-p       info-list))
+         (overloads   (-filter 'lean-info-overload-p   info-list))
+         (synths      (-filter 'lean-info-synth-p      info-list))
+         (identifiers (-filter 'lean-info-identifier-p info-list))
+         (symbols     (-filter 'lean-info-symbol-p     info-list))
+         (nay         (-filter 'lean-info-nay-p        info-list)))
+    (make-lean-info-record :type       types
+                           :overload   overloads
+                           :synth      synths
+                           :identifier identifiers
+                           :symbol     symbols
+                           :nay        nay)))
+
+(defun lean-info-record-to-string (info-record)
+  "Given typeinfo, overload, and sym-name, compose string information."
+  (let* ((type     (cl-first (lean-info-record-type       info-record)))
+         (overload (cl-first (lean-info-record-overload   info-record)))
+         (synth    (cl-first (lean-info-record-synth      info-record)))
+         (id-sym   (cl-first
+                    (append
+                     (lean-info-record-identifier info-record)
+                     (lean-info-record-symbol     info-record))))
+         name-str  type-str  overload-str str)
+    (when id-sym
+      (setq name-str (lean-info-id-symbol-body-str id-sym)))
+    (when synth
+      (setq name-str (lean-info-synth-body-str synth)))
+    (when type
+      (setq type-str (lean-info-type-body-str type)))
+    (when (and name-str overload)
+      (setq overload-str
+            (string-join
+             (--remove (string= it name-str)
+                       (lean-info-overload-names overload))
+             ", ")))
+    (when (and name-str type-str)
+      (setq str (format "%s : %s"
+                        (propertize name-str 'face 'font-lock-variable-name-face)
+                        type-str)))
+    (when overload-str
+      (setq str (concat str
+                        (format "\n%s with %s"
+                                (propertize "overloaded" 'face 'font-lock-keyword-face)
+                                overload-str))))
+    str))
+
+(defun lean-get-info-record (file-name line-number column-number)
+  "Get info list from lean server using file-name and line-number"
+  (lean-server-send-cmd (lean-cmd-visit file-name))
+  (lean-server-send-cmd (lean-cmd-info line-number))
+  (while (not lean-global-server-message-to-process)
+    (accept-process-output (lean-server-get-process) 0 50 t))
+  (pcase lean-global-server-message-to-process
+    (`(INFO ,pre ,body)
+     (lean-server-log "The following pre-message will be thrown away:")
+     (lean-server-log "%s" pre)
+     (setq lean-global-server-message-to-process nil)
+     (lean-info-record-parse body file-name column-number))
+    (`(,type ,pre , body)
+     (lean-server-log "The following pre-message will be thrown away:")
+     (lean-server-log "%s" pre)
+     (lean-server-log "Something other than INFO detected: %S" type)
+     ;; (lean-server-log "Body: %S" body)
+     (setq lean-global-server-message-to-process nil))))
+
+(defun lean-get-info-record-at-point ()
+  "Get info-record at the current point"
+  (interactive)
+  (let* ((file-name (buffer-file-name))
+         (line-number (line-number-at-pos))
+         (column (current-column))
+         (cur-char (char-after (point))))
+    (when (and cur-char
+               (or (char-equal cur-char ?\s)
+                   (char-equal cur-char ?\t)
+                   (char-equal cur-char ?\t)
+                   (char-equal cur-char ?\,)
+                   (char-equal cur-char ?\))
+                   (char-equal cur-char ?\})
+                   (char-equal cur-char ?\]))
+               (> column 1))
+      (setq column (1- column)))
+    (lean-get-info-record file-name line-number column)))
+
 (provide 'lean-info)

              ("not (eq one (succ m'))" "→ decidable (eq zero (succ m'))")
              ("not (eq two (succ m'))" "→ decidable (eq zero (succ m'))")))))
(cl-assert (equal
            (lean-overload-pos
             (lean-overload-parse
              '("-- OVERLOAD|121|2"
                "not (eq zero (succ m'))"
                "→ decidable (eq zero (succ m'))"

                "not (eq two (succ m'))"
                "→ decidable (eq zero (succ m'))")))
            '(121 2)))
(cl-assert (equal (lean-info-overload-names (lean-info-overload-parse
                                        '("-- OVERLOAD|121|2"
                                          "not (eq zero (succ m'))"
                                          "→ decidable (eq zero (succ m'))"
