dotfiles/.emacs.d/elpa/omnisharp-20171030.726/omnisharp-navigation-actions.el

221 lines
8.8 KiB
EmacsLisp

;; -*- lexical-binding: t -*-
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(defun omnisharp-go-to-definition (&optional other-window)
"Jump to the definition of the symbol under point. With prefix
argument, use another window."
(interactive "P")
(let ((gotodefinition-request (append
'((WantMetadata . t))
(omnisharp--get-request-object))))
(omnisharp--send-command-to-server
"gotodefinition"
gotodefinition-request
(lambda (response)
(omnisharp--prepare-metadata-buffer-if-needed
(omnisharp--get-filename response)
(cdr (assoc 'MetadataSource response))
(lambda (buffer filename)
(omnisharp-go-to-file-line-and-column response
other-window
buffer)))))))
(defun omnisharp--prepare-metadata-buffer-if-needed (filename
metadata-source
callback)
"Prepares metadata buffer if required (if FILENAME is missing and
METADATA-SOURCE is available) and then invokes CALLBACK with either
buffer or FILENAME of the file containing the definition.
Metadata buffer is made readonly and both omnisharp-mode and csharp-mode's
are enabled on this buffer."
(cond
;; when gotodefinition returns FileName for the same
;; metadata buffer as we're in:
;; just return current buffer
((and (boundp 'omnisharp--metadata-source)
(string-equal filename omnisharp--metadata-source))
(funcall callback (current-buffer) nil))
;; when gotodefinition returns an actual filename on the filesystem:
;; navigate to this file
(filename
(funcall callback nil filename))
;; when gotodefinition returns metadata reference:
;; in this case we need to invoke /metadata endpoint to fetch
;; generated C# source for this type from the server (unless we
;; have it already in an existing buffer)
(metadata-source
(let* ((metadata-buffer-name (omnisharp--make-metadata-buffer-name
metadata-source))
(existing-metadata-buffer (get-buffer metadata-buffer-name)))
(if existing-metadata-buffer
;; ok, we have this buffer for this metadata source loaded already
(funcall callback existing-metadata-buffer nil)
;; otherwise we need to actually retrieve metadata-generated source
;; and create a buffer for this type
(omnisharp--send-command-to-server
"metadata"
metadata-source
(lambda (response)
(let ((source (cdr (assoc 'Source response)))
(source-name (cdr (assoc 'SourceName response)))
(new-metadata-buffer (get-buffer-create metadata-buffer-name)))
(with-current-buffer new-metadata-buffer
(insert source)
(csharp-mode)
(omnisharp-mode)
(setq-local omnisharp--metadata-source source-name)
(toggle-read-only 1))
(funcall callback new-metadata-buffer nil)))))))
(t
(message
"Cannot go to definition as none was returned by the API."))))
(defun omnisharp--make-metadata-buffer-name (metadata-source)
"Builds unique buffer name for the given MetadataSource object.
This buffer name assumed to be stable and unique."
(let ((assembly-name (cdr (assoc 'AssemblyName metadata-source)))
(type-name (cdr (assoc 'TypeName metadata-source)))
(project-name (cdr (assoc 'ProjectName metadata-source))))
(concat "*omnisharp-metadata:" project-name ":" assembly-name ":" type-name "*")))
(defun omnisharp-go-to-definition-other-window ()
"Do `omnisharp-go-to-definition' displaying the result in a different window."
(interactive)
(omnisharp-go-to-definition t))
(defun omnisharp-navigate-to-current-file-member
(&optional other-window)
"Show a list of all members in the current file, and jump to the
selected member. With prefix argument, use another window."
(interactive "P")
(omnisharp--send-command-to-server
"currentfilemembersasflat"
(omnisharp--get-request-object)
(lambda (quickfixes)
(omnisharp--choose-and-go-to-quickfix-ido
quickfixes
other-window))))
(defun omnisharp-navigate-to-current-file-member-other-window ()
(interactive)
(omnisharp-navigate-to-current-file-member t))
(defun omnisharp--choose-and-go-to-quickfix-ido
(quickfixes &optional other-window)
"Given a list of QuickFixes in list format (not JSON), displays them
in an completing-read prompt and jumps to the chosen one's
Location.
If OTHER-WINDOW is given, will jump to the result in another window."
(let ((chosen-quickfix
(omnisharp--choose-quickfix-ido
(omnisharp--vector-to-list quickfixes))))
(omnisharp-go-to-file-line-and-column chosen-quickfix
other-window)))
(defun omnisharp--choose-quickfix-ido (quickfixes)
"Given a list of QuickFixes, lets the user choose one using
completing-read. Returns the chosen element."
;; Ido cannot navigate non-unique items reliably. It either gets
;; stuck, or results in that we cannot reliably determine the index
;; of the item. Work around this by prepending the index of all items
;; to their end. This makes them unique.
(let* ((quickfix-choices
(--map-indexed
(let ((this-quickfix-text (cdr (assoc 'Text it))))
(concat "#"
(number-to-string it-index)
"\t"
this-quickfix-text))
quickfixes))
(chosen-quickfix-text
(omnisharp--completing-read
"Go to: "
;; TODO use a hashmap if too slow.
;; This algorithm is two iterations in the worst case
;; scenario.
quickfix-choices))
(chosen-quickfix-index
(cl-position-if (lambda (quickfix-text)
(equal quickfix-text chosen-quickfix-text))
quickfix-choices)))
(nth chosen-quickfix-index quickfixes)))
(defun omnisharp-navigate-to-solution-member (&optional other-window)
(interactive "P")
(let ((filter (omnisharp--read-string
"Enter the start of the symbol to go to: ")))
(omnisharp--send-command-to-server
"findsymbols"
;; gets all symbols. could also filter here but ido doesn't play
;; well with changing its choices
`((Filter . ,filter))
(-lambda ((&alist 'QuickFixes quickfixes))
(omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window)))))
(defun omnisharp-navigate-to-solution-member-other-window ()
(omnisharp-navigate-to-solution-member t))
(defun omnisharp-navigate-to-solution-file (&optional other-window)
(interactive "P")
(omnisharp--send-command-to-server
"gotofile"
nil
(-lambda ((&alist 'QuickFixes quickfixes))
(omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window))))
(defun omnisharp--get-solution-files-list-of-strings ()
"Returns all files in the current solution as a list of strings."
(->> (omnisharp--get-solution-files-quickfix-response)
(assoc 'QuickFixes)
(cdr)
(omnisharp--vector-to-list)
(--map (omnisharp--get-filename it))))
(defun omnisharp-navigate-to-solution-file-then-file-member
(&optional other-window)
"Navigates to a file in the solution first, then to a member in that
file. With prefix argument uses another window."
(interactive "P")
(omnisharp-navigate-to-solution-file other-window)
;; Do not set other-window here. No need to use two different
;; windows.
(omnisharp-navigate-to-current-file-member))
(defun omnisharp-navigate-to-solution-file-then-file-member-other-window
(&optional other-window)
(omnisharp-navigate-to-solution-file-then-file-member t))
(defun omnisharp-navigate-to-region
(&optional other-window)
"Navigate to region in current file. If OTHER-WINDOW is given and t,
use another window."
(interactive "P")
(omnisharp--send-command-to-server
"gotoregion"
(omnisharp--get-request-object)
(-lambda ((&alist 'QuickFixes quickfixes))
(omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window))))
(provide 'omnisharp-navigation-actions)